From a647e2aea2dd6983e7b18260d86b494d6dcdab7e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 13:59:21 -0700 Subject: [PATCH 001/161] First test of msix package creation. --- .github/workflows/ccpp.yml | 56 ++++++++++++++++++++++++-- msix/package_staging/AppxManifest.xml | 27 +++++++++++++ msix/package_staging/logo_150.png | Bin 0 -> 43187 bytes msix/package_staging/logo_256.png | Bin 0 -> 52653 bytes msix/package_staging/logo_44.png | Bin 0 -> 18602 bytes msix/tf2-bot-detector.appinstaller | 21 ++++++++++ 6 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 msix/package_staging/AppxManifest.xml create mode 100644 msix/package_staging/logo_150.png create mode 100644 msix/package_staging/logo_256.png create mode 100644 msix/package_staging/logo_44.png create mode 100644 msix/tf2-bot-detector.appinstaller diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 9d16c37a..5fc0a6f3 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -30,12 +30,10 @@ jobs: - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME - uses: benjlevesque/short-sha@v1.1 id: short_sha - - - name: Config artifact name (tag) + - name: Config TF2BD_VERSION (tag) if: startsWith(github.ref, 'refs/tags/') run: echo "::set-env name=TF2BD_VERSION::${{ env.GIT_TAG_NAME }}" - - - name: Config artifact name (SHA) + - name: Config TF2BD_VERSION (SHA) if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" @@ -134,3 +132,53 @@ jobs: with: name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}" path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.pdb" + + + + + msix: + runs-on: windows-latest + needs: ci + strategy: + fail-fast: false + matrix: + tf2bd_arch: [x86, x64] + + steps: + - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME + - uses: benjlevesque/short-sha@v1.1 + id: short_sha + - name: Config TF2BD_VERSION (tag) + if: startsWith(github.ref, 'refs/tags/') + run: echo "::set-env name=TF2BD_VERSION::${{ env.GIT_TAG_NAME }}" + - name: Config TF2BD_VERSION (SHA) + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" + + - name: Config msix filename + run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2-bot-detector_${{ matrix.tf2bd_arch }}_${{ env.TF2BD_VERSION }}.msix" + + - name: Checkout + uses: actions/checkout@v2 + + - name: Download artifact + uses: actions/download-artifact@v2 + with: + name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}" + path: msix/package_staging/app + + - name: "Create package" + shell: bash + run: | + cd msix + ll + ll package_staging + ll package_staging/app + makeappx.exe pack /d package_staging /p ${{ env.TF2BD_MSIX_FILENAME }} + + - name: "Artifacts: msix" + uses: actions/upload-artifact@v2 + with: + path: "msix/${{ env.TF2BD_MSIX_FILENAME }}" + + diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml new file mode 100644 index 00000000..5d4b2497 --- /dev/null +++ b/msix/package_staging/AppxManifest.xml @@ -0,0 +1,27 @@ + + + + + TF2 Bot Detector + pazer + Automatically detects and votekicks cheaters/bots in TF2 casual. + logo_256.png + + + + + + + + + + + + + + + + + diff --git a/msix/package_staging/logo_150.png b/msix/package_staging/logo_150.png new file mode 100644 index 0000000000000000000000000000000000000000..2a0d39ac66cbec62c5270705294bdf442f6fed47 GIT binary patch literal 43187 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 D6LVwR literal 0 HcmV?d00001 diff --git a/msix/package_staging/logo_256.png b/msix/package_staging/logo_256.png new file mode 100644 index 0000000000000000000000000000000000000000..db00a195c8f8d3483594ce3d0b196a160b8aaa82 GIT binary patch literal 52653 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 z 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 BdFcQE literal 0 HcmV?d00001 diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller new file mode 100644 index 00000000..e9bfdac3 --- /dev/null +++ b/msix/tf2-bot-detector.appinstaller @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + From 0d46574eaf731fe1f490beae690ebccc0043609e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 14:08:37 -0700 Subject: [PATCH 002/161] Fixed missing alias. --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 5fc0a6f3..edfa7551 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -171,9 +171,9 @@ jobs: shell: bash run: | cd msix - ll - ll package_staging - ll package_staging/app + ls -la + ls -la package_staging + ls -la package_staging/app makeappx.exe pack /d package_staging /p ${{ env.TF2BD_MSIX_FILENAME }} - name: "Artifacts: msix" From b6d2f721d139d8312724024a52ef68612efe306f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 14:19:41 -0700 Subject: [PATCH 003/161] Add signing and path setup --- .github/workflows/ccpp.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index edfa7551..5192c947 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -167,6 +167,9 @@ jobs: name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}" path: msix/package_staging/app + - name: Configure PATH for windows sdk + uses: ilammy/msvc-dev-cmd@v1 + - name: "Create package" shell: bash run: | @@ -176,6 +179,13 @@ jobs: ls -la package_staging/app makeappx.exe pack /d package_staging /p ${{ env.TF2BD_MSIX_FILENAME }} + - name: Sign package + uses: PazerOP/code-sign-action@v3 + with: + folder: '${{ msix/package_staging }}' + certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' + password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + - name: "Artifacts: msix" uses: actions/upload-artifact@v2 with: From 78acb3afa0e84af4705ebbaaf98adae499d32a4e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 14:20:50 -0700 Subject: [PATCH 004/161] Switch to cmd shell --- .github/workflows/ccpp.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 5192c947..c7e67c55 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -171,12 +171,8 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: "Create package" - shell: bash run: | cd msix - ls -la - ls -la package_staging - ls -la package_staging/app makeappx.exe pack /d package_staging /p ${{ env.TF2BD_MSIX_FILENAME }} - name: Sign package From b80e9254c3e039935d53027ba66462b858def8e1 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 14:21:24 -0700 Subject: [PATCH 005/161] Fix variable --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index c7e67c55..4f1234e9 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -178,7 +178,7 @@ jobs: - name: Sign package uses: PazerOP/code-sign-action@v3 with: - folder: '${{ msix/package_staging }}' + folder: msix/package_staging certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' From 9e428a5dc7cfc86d184c04d9194b6efe97c2387f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 14:22:25 -0700 Subject: [PATCH 006/161] Fix signing dir --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 4f1234e9..0d9850ce 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -178,7 +178,7 @@ jobs: - name: Sign package uses: PazerOP/code-sign-action@v3 with: - folder: msix/package_staging + folder: msix certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' From 3f71df0c3e9101b577cf4ae381bd2948053aa769 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 14:55:04 -0700 Subject: [PATCH 007/161] Try building both x86 and x64 and bundling them together --- .github/workflows/ccpp.yml | 66 +++++++++++++++++++++++++-- msix/package_staging/AppxManifest.xml | 2 +- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 0d9850ce..1999aa14 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -158,6 +158,9 @@ jobs: - name: Config msix filename run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2-bot-detector_${{ matrix.tf2bd_arch }}_${{ env.TF2BD_VERSION }}.msix" + - name: Configure PATH for windows sdk + uses: ilammy/msvc-dev-cmd@v1 + - name: Checkout uses: actions/checkout@v2 @@ -167,24 +170,77 @@ jobs: name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}" path: msix/package_staging/app - - name: Configure PATH for windows sdk - uses: ilammy/msvc-dev-cmd@v1 + - name: Find and replace data in package Config + shell: bash + run: | + cat msix/package_staging/AppxManifest.xml \ + | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ + > msix/package_staging/AppxManifest.xml - - name: "Create package" + - name: Create msix package run: | cd msix makeappx.exe pack /d package_staging /p ${{ env.TF2BD_MSIX_FILENAME }} - - name: Sign package + - name: Sign msix package uses: PazerOP/code-sign-action@v3 with: folder: msix certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - - name: "Artifacts: msix" + - name: Upload msix package uses: actions/upload-artifact@v2 with: + name: ${{ env.TF2BD_MSIX_FILENAME }} path: "msix/${{ env.TF2BD_MSIX_FILENAME }}" + + msixbundle: + runs-on: windows-latest + needs: msix + + steps: + - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME + - uses: benjlevesque/short-sha@v1.1 + id: short_sha + - name: Config TF2BD_VERSION (tag) + if: startsWith(github.ref, 'refs/tags/') + run: echo "::set-env name=TF2BD_VERSION::${{ env.GIT_TAG_NAME }}" + - name: Config TF2BD_VERSION (SHA) + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" + + - name: Configure PATH for windows sdk + uses: ilammy/msvc-dev-cmd@v1 + + - name: Download msix (x86) + uses: actions/download-artifact@v2 + with: + name: "tf2_bot_detector_x86-windows_${{ env.TF2BD_VERSION }}.msix" + path: msix/bundle_staging + - name: Download msix (x64) + uses: actions/download-artifact@v2 + with: + name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}.msix" + path: msix/bundle_staging + + - name: Create bundle + run: | + cd msix + makeappx bundle /d bundle_staging /p tf2_bot_detector.msixbundle + + - name: Sign bundle + uses: PazerOP/code-sign-action@v3 + with: + folder: msix + certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' + password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + + - name: Upload bundle + uses: actions/upload-artifact@v2 + with: + name: tf2_bot_detector.msixbundle + path: "msix/tf2_bot_detector.msixbundle" + diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 5d4b2497..9b13b73a 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -2,7 +2,7 @@ - + TF2 Bot Detector pazer From 511ce9cb9f06e9b55e2a20ff6fec520790b650a6 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 15:06:44 -0700 Subject: [PATCH 008/161] Something wrong with x64 appxmanifest modifications? --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 1999aa14..e341386d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -175,7 +175,7 @@ jobs: run: | cat msix/package_staging/AppxManifest.xml \ | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ - > msix/package_staging/AppxManifest.xml + | tee msix/package_staging/AppxManifest.xml - name: Create msix package run: | From 5459ca81562cea0cd026b02e6d0a7068a8ba105c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 15:16:33 -0700 Subject: [PATCH 009/161] Change msix filename to be consistent with other artifacts --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index e341386d..08b0e4bd 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -156,7 +156,7 @@ jobs: run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" - name: Config msix filename - run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2-bot-detector_${{ matrix.tf2bd_arch }}_${{ env.TF2BD_VERSION }}.msix" + run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2-bot-detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}.msix" - name: Configure PATH for windows sdk uses: ilammy/msvc-dev-cmd@v1 From c51026d45423da1d562585f600021b7fbfe7cde4 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 15:27:26 -0700 Subject: [PATCH 010/161] fixed artifact name... again --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 08b0e4bd..94f8d16c 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -156,7 +156,7 @@ jobs: run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" - name: Config msix filename - run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2-bot-detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}.msix" + run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}.msix" - name: Configure PATH for windows sdk uses: ilammy/msvc-dev-cmd@v1 From 9a7e1c8ebf0997a14052111cc66d321fc9e86800 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 16:42:32 -0700 Subject: [PATCH 011/161] Update appinstaller with azure function redirect to github actions artifact --- msix/tf2-bot-detector.appinstaller | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index e9bfdac3..006b9ba8 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -7,8 +7,7 @@ + Uri="https://tf2bd-github-api.azurewebsites.net/api/GetGenericArtifact?code=dZm3sePk5wwYGRtoMmzcj2GUUWqlYaDQTnGL97XwE7DC1NPUw6IUag%3D%3D&owner=PazerOP&repo=tf2_bot_detector&artifactID=14667276" /> From df89a7f50260e87fcc14f4c2c9f68bf5327c0622 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 16:53:12 -0700 Subject: [PATCH 012/161] Whoops, that won't work because of github double-zipping artifacts --- msix/tf2-bot-detector.appinstaller | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index 006b9ba8..f993dc4c 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -4,10 +4,10 @@ Version="1.0.0.0" Uri="https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller"> - + Uri="https://github.com/PazerOP/tf2_bot_detector/releases/download/1.1.0.10/test_dont_download.msixbundle" /> From 1ca03e957494535b927793dc7048e06700f44826 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 17:10:07 -0700 Subject: [PATCH 013/161] Pretty sure this doesn't work because of the redirect, or because app installer is requesting multiple sets or ranges from the backing amazon s3 hosting. --- msix/tf2-bot-detector.appinstaller | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index f993dc4c..edf4d2b6 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -4,10 +4,10 @@ Version="1.0.0.0" Uri="https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller"> - + Uri="https://github.com/PazerOP/tf2_bot_detector/releases/download/1.1.0.10/test_dont_download.msix" /> From fa3ad5801821afe34a5cfc8994884be5008c79d2 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 17:14:48 -0700 Subject: [PATCH 014/161] =?UTF-8?q?=F0=9F=A4=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- msix/tf2-bot-detector.appinstaller | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index edf4d2b6..b19f29ad 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -7,7 +7,7 @@ + Uri="https://puu.sh/GjITB/7d3ad8b51f.msixbundle" /> From ebebe65e13a99bef6061c61c2cdeac60bc511383 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 20:22:40 -0700 Subject: [PATCH 015/161] Fix the version in the appx bundle. --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 94f8d16c..e9df16ea 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -229,7 +229,7 @@ jobs: - name: Create bundle run: | cd msix - makeappx bundle /d bundle_staging /p tf2_bot_detector.msixbundle + makeappx bundle /d bundle_staging /bv 1.1.0.11 /p tf2_bot_detector.msixbundle - name: Sign bundle uses: PazerOP/code-sign-action@v3 From eac67f8af7d596c970ab4a5af38f4229a8a11848 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 20:23:02 -0700 Subject: [PATCH 016/161] Update the current (almost working) version of the .appinstaller. --- msix/tf2-bot-detector.appinstaller | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index b19f29ad..7ba5f2e0 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -1,17 +1,17 @@ + Version="1.0.1.0" + Uri="http://home.pazer.us/tf2_bot_detector/tf2-bot-detector.appinstaller"> - + Uri="http://home.pazer.us/tf2_bot_detector/tf2bd.msixbundle" /> - - + + From 38abc77d18de2b3b0e75b444d998202f4b2aa86f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 20:23:21 -0700 Subject: [PATCH 017/161] Fix the vcredist dependency. --- msix/package_staging/AppxManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 9b13b73a..4e2e602f 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -14,7 +14,7 @@ - + From 10335bd8b08536f2d390d9b75598a69e957de166 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 21:15:42 -0700 Subject: [PATCH 018/161] Add support for msixcore --- msix/package_staging/AppxManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 4e2e602f..f2518183 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -14,6 +14,7 @@ + From 05e8d80337e04d3f0db0209f175074da52ce9525 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 22:14:34 -0700 Subject: [PATCH 019/161] Fix urls to point to github where possible --- msix/tf2-bot-detector.appinstaller | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index 7ba5f2e0..40668cec 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -2,12 +2,12 @@ + Uri="https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller"> + Uri="http://home.pazer.us/tf2_bot_detector/tf2-bot-detector.msixbundle" /> From cb4bc0e55627ba86c10456a4c6bd527e4d23e87e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 22:18:43 -0700 Subject: [PATCH 020/161] Add install link to readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index d93628b0..64854257 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,12 @@ This program requires [Microsoft Visual C++ Redistributable for Visual Studio 20 ### Installation +#### Automatic Install + +If you are using Windows 10 1709 or newer, just click this link: [Install][msix-install-link] + +#### Manual/Portable Install + 1. Download and install the [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019][mscr-link] 2. Download the [latest release][latest-64] ([32bit version][latest-86]) 3. Extract the zip in any location inside of your user folder (e.g. Downloads, Documents) @@ -182,6 +188,7 @@ Huge thanks to the people sponsoring this project via [GitHub Sponsors][github-s [discord-link]: https://discord.gg/W8ZSh3Z [mscr-link]: https://aka.ms/vs/16/release/vc_redist.x64.exe [mscr86-link]: https://aka.ms/vs/16/release/vc_redist.x86.exe +[msix-install-link]: ms-appinstaller:?source=https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller [zip-image]: https://i.imgur.com/ZeCuUul.png [github-sponsors-pazerop]: https://github.com/sponsors/PazerOP [wiki-customization-link]: https://github.com/PazerOP/tf2_bot_detector/wiki/Customization#third-party-player-lists From c74ae8b160b14afd4838e3ee2448ac2e58805b1b Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 22:33:29 -0700 Subject: [PATCH 021/161] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 64854257..c66bcc5b 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ Huge thanks to the people sponsoring this project via [GitHub Sponsors][github-s [discord-link]: https://discord.gg/W8ZSh3Z [mscr-link]: https://aka.ms/vs/16/release/vc_redist.x64.exe [mscr86-link]: https://aka.ms/vs/16/release/vc_redist.x86.exe -[msix-install-link]: ms-appinstaller:?source=https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller +[msix-install-link]: https://tf2bd-github-api.azurewebsites.net/api/AppInstallerRedirect?source=https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller [zip-image]: https://i.imgur.com/ZeCuUul.png [github-sponsors-pazerop]: https://github.com/sponsors/PazerOP [wiki-customization-link]: https://github.com/PazerOP/tf2_bot_detector/wiki/Customization#third-party-player-lists From 21e05f4e66b4fbebfd1810d87c5b1637e7cc3f15 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 19 Aug 2020 22:35:43 -0700 Subject: [PATCH 022/161] Added Install link to the header --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c66bcc5b..ae692e50 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@
-->
+
Install + · Report a Bug · Request a Feature From 8007344ee1e4c1ec13b99d19b9ad089e33892e56 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 00:53:40 -0700 Subject: [PATCH 023/161] Add an indication to the app that it should not attempt to write to the installation directory. --- .github/workflows/ccpp.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index e9df16ea..839dc41b 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -170,6 +170,10 @@ jobs: name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}" path: msix/package_staging/app + - name: Mark as non-portable + shell: bash + run: touch msix/package_staging/app/cfg/.non_portable + - name: Find and replace data in package Config shell: bash run: | From 469e1c94874501daa9ae74b8865f791fc9b47347 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 11:18:26 -0700 Subject: [PATCH 024/161] Removed some unnecessary includes. --- tf2_bot_detector/Actions/RCONActionManager.cpp | 1 - tf2_bot_detector/Config/PlayerListJSON.cpp | 1 - tf2_bot_detector/Config/Rules.cpp | 1 - tf2_bot_detector/ModeratorLogic.cpp | 5 ----- 4 files changed, 8 deletions(-) diff --git a/tf2_bot_detector/Actions/RCONActionManager.cpp b/tf2_bot_detector/Actions/RCONActionManager.cpp index 7e3b8886..c342e1bf 100644 --- a/tf2_bot_detector/Actions/RCONActionManager.cpp +++ b/tf2_bot_detector/Actions/RCONActionManager.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include diff --git a/tf2_bot_detector/Config/PlayerListJSON.cpp b/tf2_bot_detector/Config/PlayerListJSON.cpp index 311edba1..856f7f7a 100644 --- a/tf2_bot_detector/Config/PlayerListJSON.cpp +++ b/tf2_bot_detector/Config/PlayerListJSON.cpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include diff --git a/tf2_bot_detector/Config/Rules.cpp b/tf2_bot_detector/Config/Rules.cpp index 72e840d0..aa189815 100644 --- a/tf2_bot_detector/Config/Rules.cpp +++ b/tf2_bot_detector/Config/Rules.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include diff --git a/tf2_bot_detector/ModeratorLogic.cpp b/tf2_bot_detector/ModeratorLogic.cpp index de9c048f..daad3f3d 100644 --- a/tf2_bot_detector/ModeratorLogic.cpp +++ b/tf2_bot_detector/ModeratorLogic.cpp @@ -15,12 +15,7 @@ #include #include -#include -#include -#include #include -#include -#include #include #include From 54a3fe15a8a0218dd93bcae41e5370fe1c43663c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 12:00:52 -0700 Subject: [PATCH 025/161] Switch to the tweak being the overall build number --- .github/workflows/ccpp.yml | 5 ++++- CMakeLists.txt | 2 +- msix/package_staging/AppxManifest.xml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 839dc41b..47830a03 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -54,6 +54,8 @@ jobs: echo "github.event_name = ${{ github.event_name }}" echo "github.sha = ${{ github.sha }}" echo "github.ref = ${{ github.ref }}" + echo "github.run_id = ${{ github.run_id }}" + echo "github.run_number = ${{ github.run_number }}" echo "matrix.os = ${{ matrix.os }}" echo "matrix.triplet = ${{ matrix.triplet }}" echo "matrix.discord_integration = ${{ matrix.discord_integration }}" @@ -84,7 +86,7 @@ jobs: run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -A${{ matrix.build_arch }} -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} ../ + cmake -A${{ matrix.build_arch }} -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ cmake --build . --config Release - name: Sign artifacts @@ -179,6 +181,7 @@ jobs: run: | cat msix/package_staging/AppxManifest.xml \ | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ + | sed 's/TF2BD_BUILD_NUMBER_REPLACE_ME/${{ github.run_number }}/g' \ | tee msix/package_staging/AppxManifest.xml - name: Create msix package diff --git a/CMakeLists.txt b/CMakeLists.txt index bf04c5fc..60f62521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.17.2) -project(tf2_bot_detector VERSION 1.1.0.10) +project(tf2_bot_detector VERSION 1.1.0.12) include(GenerateExportHeader) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index f2518183..af72bb0b 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -2,7 +2,7 @@ - + TF2 Bot Detector pazer From 67501fdd7e8946c98c9bf72388ca550b4ff53cf0 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 12:03:25 -0700 Subject: [PATCH 026/161] Also print out the tweak number from cmake. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 60f62521..55242990 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(tf2_bot_detector VERSION 1.1.0.12) include(GenerateExportHeader) message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") +message("TF2BD CMAKE_PROJECT_VERSION_TWEAK = ${CMAKE_PROJECT_VERSION_TWEAK}") option(TF2BD_IS_CI_COMPILE "Set to true if this is a compile on a CI service. Used to help determine if user has made modifications to the source code." off) if (TF2BD_IS_CI_COMPILE) From e3ca433a6f4a25310cde79fa9a62b4ce0d2b9e4f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 14:25:13 -0700 Subject: [PATCH 027/161] Changes to support non-writeable application directories. --- .gitignore | 1 + CMakeLists.txt | 1 + tf2_bot_detector/Config/ConfigHelpers.cpp | 188 ++++++++++++++++-- tf2_bot_detector/Config/ConfigHelpers.h | 10 +- tf2_bot_detector/Config/Settings.cpp | 41 ++-- tf2_bot_detector/Log.cpp | 38 ++++ tf2_bot_detector/Log.h | 29 ++- tf2_bot_detector/Platform/Platform.h | 3 + tf2_bot_detector/Platform/Windows/Windows.cpp | 32 +++ 9 files changed, 305 insertions(+), 38 deletions(-) create mode 100644 tf2_bot_detector/Platform/Windows/Windows.cpp diff --git a/.gitignore b/.gitignore index fcb9fad5..3fdd668d 100644 --- a/.gitignore +++ b/.gitignore @@ -346,3 +346,4 @@ staging/cfg/settings.json out/ staging/debug_report.zip staging/cfg/playerlist.json +staging/cfg/.non_portable diff --git a/CMakeLists.txt b/CMakeLists.txt index 55242990..6c091c8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,6 +178,7 @@ if(WIN32) "tf2_bot_detector/Platform/Windows/Steam.cpp" "tf2_bot_detector/Platform/Windows/WindowsHelpers.h" "tf2_bot_detector/Resources.rc" + "tf2_bot_detector/Platform/Windows/Windows.cpp" ) endif() diff --git a/tf2_bot_detector/Config/ConfigHelpers.cpp b/tf2_bot_detector/Config/ConfigHelpers.cpp index 91982cc8..76a243f6 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.cpp +++ b/tf2_bot_detector/Config/ConfigHelpers.cpp @@ -1,5 +1,6 @@ #include "ConfigHelpers.h" #include "Networking/HTTPHelpers.h" +#include "Platform/Platform.h" #include "Util/JSONUtils.h" #include "Util/RegexUtils.h" #include "Log.h" @@ -16,11 +17,161 @@ using namespace std::string_literals; using namespace std::string_view_literals; using namespace tf2_bot_detector; +namespace +{ + class ConfigSystem final + { + public: + ConfigSystem(); + + const std::filesystem::path& GetAppDataDir() const { return m_AppDataDir; } + const std::filesystem::path& GetExeDir() const { return m_ExeDir; } + const std::filesystem::path& GetMutableDataDir() const { return m_MutableDataDir; } + bool IsPortable() const { return m_IsPortable; } + + private: + static constexpr char NON_PORTABLE_MARKER[] = ".non_portable"; + + bool CheckIsPortable() const; + std::filesystem::path CreateAppDataPath() const; + std::filesystem::path ChooseMutableDataPath() const; + + std::filesystem::path m_ExeDir = Platform::GetCurrentExeDir(); + std::filesystem::path m_WorkingDir = std::filesystem::current_path(); + std::filesystem::path m_AppDataDir = CreateAppDataPath(); + std::filesystem::path m_MutableDataDir = ChooseMutableDataPath(); + bool m_IsPortable = CheckIsPortable(); + }; + + ConfigSystem::ConfigSystem() try + { + DebugLog("Initializing config system..." + "\n\t Executable dir : {}" + "\n\t Working dir : {}" + "\n\t AppData dir : {}" + "\n\tMutable data dir : {}" + ,GetExeDir() + ,m_WorkingDir + ,m_AppDataDir + ,GetMutableDataDir() + ); + + if (GetMutableDataDir() != GetExeDir()) + { + DebugLog("Mutable data path is {}, while exe path is {}. Preparing to copy missing files to mutable data path...", + GetMutableDataDir(), GetExeDir()); + + std::filesystem::create_directories(GetMutableDataDir()); + + const auto sourcePath = GetExeDir() / "cfg"; + if (!std::filesystem::exists(sourcePath)) + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), + "\"cfg\" folder not found next to exe, not copying shipped cfg files to mutable data directory {}", + GetMutableDataDir()); + } + else + { + const auto destPath = GetMutableDataDir() / "cfg"; + + // Copy directory structure + std::filesystem::copy(sourcePath, destPath, std::filesystem::copy_options::directories_only); + + for (const auto entry : std::filesystem::recursive_directory_iterator(sourcePath)) + { + const auto relativePath = std::filesystem::relative(entry.path(), sourcePath); + const auto fullDestPath = destPath / relativePath; + + if (!entry.is_regular_file()) + continue; + + // FIXME: kind of an awful hack, shouldn't just have this hardcoded like this + if (entry.path().filename() == "settings.json") + continue; + + std::filesystem::copy_file(entry.path(), fullDestPath, + std::filesystem::copy_options::overwrite_existing); + } + } + } + } + catch (const std::exception& e) + { + LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to initialize config system"); + } + + bool ConfigSystem::CheckIsPortable() const try + { + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Current mutable data path: {}", GetMutableDataDir()); + const auto shippedCfgPath = GetMutableDataDir() / "cfg"; + + if (!std::filesystem::exists(shippedCfgPath)) + { + LogFatalError(MH_SOURCE_LOCATION_CURRENT(), + "The folder {} is missing or invalid.\nIt contains required files for this program.", shippedCfgPath); + } + + const auto nonPortablePath = shippedCfgPath / NON_PORTABLE_MARKER; + if (std::filesystem::exists(nonPortablePath)) + { + DebugLog("Installation is non-portable because marker file exists ({})", nonPortablePath); + return true; + } + else + { + DebugLog("Installation is portable because marker file does not exist ({})", nonPortablePath); + return false; + } + + return true; + } + catch (const std::exception& e) + { + LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to determine install type (installed/portable)"); + } + + std::filesystem::path ConfigSystem::CreateAppDataPath() const + { + return Platform::GetAppDataDir() / "TF2 Bot Detector"; + } + + std::filesystem::path ConfigSystem::ChooseMutableDataPath() const try + { + // Check the working directory for a cfg folder + auto mutableDir = m_WorkingDir; + if (!std::filesystem::exists(mutableDir / "cfg")) + mutableDir = m_ExeDir; + + if (!std::filesystem::exists(mutableDir / "cfg")) + { + LogFatalError(MH_SOURCE_LOCATION_CURRENT(), + "\"cfg\" folder not found in either the exe directory or working directory"); + } + + return mutableDir; + } + catch (const std::exception& e) + { + LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to choose mutable data path"); + } + + static ConfigSystem& GetConfigSystem() + { + static ConfigSystem s_ConfigSystem; + return s_ConfigSystem; + } +} + +const std::filesystem::path& tf2_bot_detector::GetMutableDataPath() +{ + return GetConfigSystem().GetMutableDataDir(); +} + auto tf2_bot_detector::GetConfigFilePaths(const std::string_view& basename) -> ConfigFilePaths { ConfigFilePaths retVal; - const std::filesystem::path cfg("cfg"); + const std::filesystem::path cfg = GetMutableDataPath() / "cfg"; if (std::filesystem::is_directory(cfg)) { try @@ -42,7 +193,8 @@ auto tf2_bot_detector::GetConfigFilePaths(const std::string_view& basename) -> C } catch (const std::filesystem::filesystem_error& e) { - LogError(std::string(__FUNCTION__ ": Exception when loading playerlist.*.json files from ./cfg/: ") << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to gather names matching {}.*.json in {}", basename, cfg); } } @@ -73,20 +225,30 @@ static ConfigSchemaInfo LoadAndValidateSchema(const ConfigFileBase& config, cons return schema; } -static bool TryAutoUpdate(const std::filesystem::path& filename, const nlohmann::json& existingJson, +static bool TryAutoUpdate(std::filesystem::path filename, const nlohmann::json& existingJson, SharedConfigFileBase& config, const HTTPClient& client) { + try + { + filename = std::filesystem::absolute(filename); + } + catch (const std::exception& e) + { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to convert {} to absolute path", filename); + return false; + } + auto fileInfoJson = existingJson.find("file_info"); if (fileInfoJson == existingJson.end()) { - DebugLog("Skipping auto-update of "s << filename << ": file_info object missing"); + DebugLog("Skipping auto-update of {}: file_info object missing", filename); return false; } const ConfigFileInfo info(*fileInfoJson); if (info.m_UpdateURL.empty()) { - DebugLog("Skipping auto-update of "s << filename << ": update_url was empty"); + DebugLog("Skipping auto-update of {}: update_url was empty", filename); return false; } @@ -97,8 +259,8 @@ static bool TryAutoUpdate(const std::filesystem::path& filename, const nlohmann: } catch (const std::exception& e) { - LogError("Failed to auto-update "s << filename << ": failed to parse new json from " - << info.m_UpdateURL << ": " << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to auto-update {}: failed to parse new json from {}", filename, info.m_UpdateURL); return false; } @@ -108,8 +270,8 @@ static bool TryAutoUpdate(const std::filesystem::path& filename, const nlohmann: } catch (const std::exception& e) { - LogError("Failed to auto-update "s << filename << " from " << info.m_UpdateURL - << ": new json failed schema validation: " << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to auto-update {} from {}: new json failed schema validation", filename, info.m_UpdateURL); return false; } @@ -121,8 +283,8 @@ static bool TryAutoUpdate(const std::filesystem::path& filename, const nlohmann: } catch (const std::exception& e) { - LogError("Failed to auto-update "s << filename << " from " << info.m_UpdateURL - << ": failed to parse file info from new json: " << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to auto-update {} from {}: failed to parse file info from new json", filename, info.m_UpdateURL); return false; } @@ -135,8 +297,8 @@ static bool TryAutoUpdate(const std::filesystem::path& filename, const nlohmann: } catch (const std::exception& e) { - LogError("Skipping auto-update of "s << filename << ": failed to deserialize response from " - << info.m_UpdateURL << ": " << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to auto-update {}: failed to deserialize response from {}", filename, info.m_UpdateURL); return false; } diff --git a/tf2_bot_detector/Config/ConfigHelpers.h b/tf2_bot_detector/Config/ConfigHelpers.h index 0ef9de63..d89d89eb 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.h +++ b/tf2_bot_detector/Config/ConfigHelpers.h @@ -23,6 +23,10 @@ namespace tf2_bot_detector COUNT, }; + // If this is a portable install, the application directory + // If this is a non-portable install, %APPDATA%/TF2 Bot Detector + const std::filesystem::path& GetMutableDataPath(); + struct ConfigFilePaths { std::filesystem::path m_User; @@ -180,14 +184,14 @@ namespace tf2_bot_detector const T* defaultMutableList = GetDefaultMutableList(); const T* localList = GetLocalList(); if (localList) - localList->SaveFile(mh::format("cfg/{}.json", GetBaseFileName())); + localList->SaveFile(GetMutableDataPath() / "cfg" / mh::format("{}.json", GetBaseFileName())); if (defaultMutableList && defaultMutableList != localList) { - const auto filename = mh::format("cfg/{}.official.json", GetBaseFileName()); + const auto filename = GetMutableDataPath() / "cfg" / mh::format("{}.official.json", GetBaseFileName()); if (!IsOfficial()) - throw std::runtime_error("Attempted to save non-official data to "s << std::quoted(filename)); + throw std::runtime_error("Attempted to save non-official data to "s << filename); defaultMutableList->SaveFile(filename); } diff --git a/tf2_bot_detector/Config/Settings.cpp b/tf2_bot_detector/Config/Settings.cpp index cb2cb748..bbea8773 100644 --- a/tf2_bot_detector/Config/Settings.cpp +++ b/tf2_bot_detector/Config/Settings.cpp @@ -22,7 +22,11 @@ using namespace tf2_bot_detector; using namespace std::string_literals; using namespace std::string_view_literals; -static const std::filesystem::path s_SettingsPath("cfg/settings.json"); +static const std::filesystem::path& GetSettingsPath() +{ + static const auto s_SettingsPath = tf2_bot_detector::GetMutableDataPath() / "cfg" / "settings.json"; + return s_SettingsPath; +} namespace tf2_bot_detector { @@ -181,10 +185,11 @@ void Settings::LoadFile() { nlohmann::json json; { - std::ifstream file(s_SettingsPath); + const auto& settingsPath = GetSettingsPath(); + std::ifstream file(settingsPath); if (!file.good()) { - LogError(std::string(__FUNCTION__ ": Failed to open ") << s_SettingsPath); + LogError(MH_SOURCE_LOCATION_CURRENT(), "Failed to open {}", settingsPath); } else { @@ -194,14 +199,19 @@ void Settings::LoadFile() } catch (const nlohmann::json::exception& e) { - auto backupPath = std::filesystem::path(s_SettingsPath).replace_filename("settings.backup.json"); - LogError(std::string(__FUNCTION__) << ": Failed to parse JSON from " << s_SettingsPath << ": " << e.what() - << ". Writing backup to "); - - std::error_code ec; - std::filesystem::copy_file(s_SettingsPath, backupPath, ec); - if (!ec) - LogError(std::string(__FUNCTION__) << ": Failed to make backup of settings.json to " << backupPath); + const auto backupPath = std::filesystem::path(settingsPath).replace_filename("settings.backup.json"); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to parse JSON from {}. Writing backup to {}...", settingsPath, backupPath); + + try + { + std::filesystem::copy_file(settingsPath, backupPath); + } + catch (const std::exception& e) + { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to make backup of settings.json to {}", backupPath); + } } } } @@ -285,18 +295,19 @@ bool Settings::SaveFile() const try // Make sure we successfully serialize BEFORE we destroy our file auto jsonString = json.dump(1, '\t', true); { - std::filesystem::create_directories(std::filesystem::path(s_SettingsPath).remove_filename()); - std::ofstream file(s_SettingsPath, std::ios::binary); + const auto& settingsPath = GetSettingsPath(); + std::filesystem::create_directories(std::filesystem::path(settingsPath).remove_filename()); + std::ofstream file(settingsPath, std::ios::binary); if (!file.good()) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "Failed to open settings file for writing: {}", s_SettingsPath); + LogError(MH_SOURCE_LOCATION_CURRENT(), "Failed to open settings file for writing: {}", settingsPath); return false; } file << jsonString << '\n'; if (!file.good()) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "Failed to write settings to {}", s_SettingsPath); + LogError(MH_SOURCE_LOCATION_CURRENT(), "Failed to write settings to {}", settingsPath); return false; } } diff --git a/tf2_bot_detector/Log.cpp b/tf2_bot_detector/Log.cpp index 971bffaf..69b65fdc 100644 --- a/tf2_bot_detector/Log.cpp +++ b/tf2_bot_detector/Log.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -208,6 +209,43 @@ void tf2_bot_detector::LogException(const mh::source_location& location, const s LogError(location, msg.empty() ? "{1}: {2}" : "{0}: {1}: {2}", msg, typeid(e).name(), e.what()); } +void tf2_bot_detector::LogFatalError(const mh::source_location& location, const std::string_view& msg) +{ + LogError(location, msg); + + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", + mh::format( +R"({} + +Error source filename: {}:{} +Error source function: {})" + , msg, location.file_name(), location.line(), location.function_name() + ).c_str(), nullptr); + + std::exit(1); +} + +void tf2_bot_detector::LogFatalException(const mh::source_location& location, const std::exception& e, + const std::string_view& msg) +{ + LogException(location, e, msg); + + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error", + mh::format( +R"({} + +Exception type: {} +Exception message: {} + +Exception source filename: {}:{} +Exception source function: {})" + , msg, typeid(e).name(), e.what(), + location.file_name(), location.line(), location.function_name() + ).c_str(), nullptr); + + std::exit(1); +} + void LogManager::Log(std::string msg, const LogMessageColor & color, LogSeverity severity, LogVisibility visibility, time_point_t timestamp) { diff --git a/tf2_bot_detector/Log.h b/tf2_bot_detector/Log.h index f57bc346..96bea671 100644 --- a/tf2_bot_detector/Log.h +++ b/tf2_bot_detector/Log.h @@ -97,7 +97,7 @@ namespace tf2_bot_detector #undef LOG_DEFINITION_HELPER #define LOG_DEFINITION_HELPER(name, defaultColor, severity, visibility) \ template 0)>> \ - inline void name(const LogMessageColor& color, const std::string_view& fmtStr, const TArgs&... args) \ + NOINLINE inline void name(const LogMessageColor& color, const std::string_view& fmtStr, const TArgs&... args) \ { \ ILogManager::GetInstance().Log(mh::format(fmtStr, args...), color, (severity), (visibility)); \ } \ @@ -116,7 +116,7 @@ namespace tf2_bot_detector } \ \ template \ - inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ + NOINLINE inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ { \ std::string msg = mh::format("{}@{}:{}()", location.file_name(), location.line(), location.function_name()); \ if (!fmtStr.empty()) \ @@ -145,15 +145,30 @@ namespace tf2_bot_detector LOG_DEFINITION_HELPER(LogError, LogColors::ERROR, LogSeverity::Error, LogVisibility::Default); #undef LOG_DEFINITION_HELPER + +#define LOG_DEFINITION_HELPER(name, attr) \ + attr void name(const mh::source_location& location, const std::exception& e, const std::string_view& msg = {}); \ + \ + template 0)>> \ + attr void name(const mh::source_location& location, const std::exception& e, \ + const std::string_view& fmtStr, const TArgs&... args) \ + { \ + name(location, e, mh::format(fmtStr, args...)); \ + } + + LOG_DEFINITION_HELPER(LogException, ); + LOG_DEFINITION_HELPER(LogFatalException, [[noreturn]]); + +#undef LOG_DEFINITION_HELPER + #undef NOINLINE #pragma pop_macro("NOINLINE") - void LogException(const mh::source_location& location, const std::exception& e, const std::string_view& msg = {}); - + [[noreturn]] void LogFatalError(const mh::source_location& location, const std::string_view& msg); template 0)>> - void LogException(const mh::source_location& location, const std::exception& e, - const std::string_view& fmtStr, const TArgs&... args) + [[noreturn]] void LogFatalError(const mh::source_location& location, + const std::string_view& fmtStr, const TArgs&... args) { - LogException(location, e, mh::format(fmtStr, args...)); + LogFatalError(location, mh::format(fmtStr, args...)); } } diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 8e65cc0c..7dabe32a 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -14,6 +14,9 @@ namespace tf2_bot_detector std::filesystem::path GetCurrentSteamDir(); SteamID GetCurrentActiveSteamID(); + std::filesystem::path GetCurrentExeDir(); + std::filesystem::path GetAppDataDir(); + namespace Processes { bool IsTF2Running(); diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp new file mode 100644 index 00000000..1d585d97 --- /dev/null +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -0,0 +1,32 @@ +#include "Platform/Platform.h" +#include "WindowsHelpers.h" + +#define WIN32_LEAN_AND_MEAN 1 +#include +#include + +std::filesystem::path tf2_bot_detector::Platform::GetCurrentExeDir() +{ + WCHAR path[32768]; + const auto length = GetModuleFileNameW(nullptr, path, std::size(path)); + + const auto error = GetLastError(); + if (error != ERROR_SUCCESS) + throw tf2_bot_detector::Windows::GetLastErrorException(E_FAIL, error, "Call to GetModuleFileNameW() failed"); + + if (length == 0) + throw std::runtime_error("Call to GetModuleFileNameW() failed: return value was 0"); + + return std::filesystem::path(path, path + length).remove_filename(); +} + +std::filesystem::path tf2_bot_detector::Platform::GetAppDataDir() +{ + PWSTR str; + CHECK_HR(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &str)); + + std::filesystem::path retVal(str); + + CoTaskMemFree(str); + return retVal; +} From adfdc50df23b50648ab30fabed6fb266ac02e193 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 17:08:45 -0700 Subject: [PATCH 028/161] Moving towards better filesystem handling --- CMakeLists.txt | 2 + tf2_bot_detector/BaseTextures.cpp | 7 +- tf2_bot_detector/Config/ConfigHelpers.cpp | 152 +----------------- tf2_bot_detector/Config/ConfigHelpers.h | 8 +- tf2_bot_detector/Config/Settings.cpp | 10 +- tf2_bot_detector/Filesystem.cpp | 179 ++++++++++++++++++++++ tf2_bot_detector/Filesystem.h | 35 +++++ 7 files changed, 229 insertions(+), 164 deletions(-) create mode 100644 tf2_bot_detector/Filesystem.cpp create mode 100644 tf2_bot_detector/Filesystem.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c091c8f..11ac2d4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,8 @@ target_sources(tf2_bot_detector PRIVATE "tf2_bot_detector/Config/ChatWrappers.h" "tf2_bot_detector/DLLMain.cpp" "tf2_bot_detector/DLLMain.h" + "tf2_bot_detector/Filesystem.cpp" + "tf2_bot_detector/Filesystem.h" "tf2_bot_detector/IPlayer.cpp" "tf2_bot_detector/IPlayer.h" "tf2_bot_detector/Log.cpp" diff --git a/tf2_bot_detector/BaseTextures.cpp b/tf2_bot_detector/BaseTextures.cpp index f8e4a20b..ed9da6af 100644 --- a/tf2_bot_detector/BaseTextures.cpp +++ b/tf2_bot_detector/BaseTextures.cpp @@ -1,5 +1,6 @@ #include "BaseTextures.h" #include "Bitmap.h" +#include "Filesystem.h" #include "Log.h" #include "TextureManager.h" @@ -29,7 +30,7 @@ namespace std::shared_ptr m_VACShield_16; std::shared_ptr m_GameBanIcon_16; - std::shared_ptr TryLoadTexture(const std::filesystem::path& file) const; + std::shared_ptr TryLoadTexture(std::filesystem::path file) const; }; } @@ -47,8 +48,10 @@ BaseTextures::BaseTextures(ITextureManager& textureManager) : { } -std::shared_ptr BaseTextures::TryLoadTexture(const std::filesystem::path& file) const +std::shared_ptr BaseTextures::TryLoadTexture(std::filesystem::path file) const { + file = IFilesystem::Get().ResolvePath(file, PathUsage::Read); + try { return m_TextureManager.CreateTexture(Bitmap(file)); diff --git a/tf2_bot_detector/Config/ConfigHelpers.cpp b/tf2_bot_detector/Config/ConfigHelpers.cpp index 76a243f6..df29f6aa 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.cpp +++ b/tf2_bot_detector/Config/ConfigHelpers.cpp @@ -17,161 +17,11 @@ using namespace std::string_literals; using namespace std::string_view_literals; using namespace tf2_bot_detector; -namespace -{ - class ConfigSystem final - { - public: - ConfigSystem(); - - const std::filesystem::path& GetAppDataDir() const { return m_AppDataDir; } - const std::filesystem::path& GetExeDir() const { return m_ExeDir; } - const std::filesystem::path& GetMutableDataDir() const { return m_MutableDataDir; } - bool IsPortable() const { return m_IsPortable; } - - private: - static constexpr char NON_PORTABLE_MARKER[] = ".non_portable"; - - bool CheckIsPortable() const; - std::filesystem::path CreateAppDataPath() const; - std::filesystem::path ChooseMutableDataPath() const; - - std::filesystem::path m_ExeDir = Platform::GetCurrentExeDir(); - std::filesystem::path m_WorkingDir = std::filesystem::current_path(); - std::filesystem::path m_AppDataDir = CreateAppDataPath(); - std::filesystem::path m_MutableDataDir = ChooseMutableDataPath(); - bool m_IsPortable = CheckIsPortable(); - }; - - ConfigSystem::ConfigSystem() try - { - DebugLog("Initializing config system..." - "\n\t Executable dir : {}" - "\n\t Working dir : {}" - "\n\t AppData dir : {}" - "\n\tMutable data dir : {}" - ,GetExeDir() - ,m_WorkingDir - ,m_AppDataDir - ,GetMutableDataDir() - ); - - if (GetMutableDataDir() != GetExeDir()) - { - DebugLog("Mutable data path is {}, while exe path is {}. Preparing to copy missing files to mutable data path...", - GetMutableDataDir(), GetExeDir()); - - std::filesystem::create_directories(GetMutableDataDir()); - - const auto sourcePath = GetExeDir() / "cfg"; - if (!std::filesystem::exists(sourcePath)) - { - DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), - "\"cfg\" folder not found next to exe, not copying shipped cfg files to mutable data directory {}", - GetMutableDataDir()); - } - else - { - const auto destPath = GetMutableDataDir() / "cfg"; - - // Copy directory structure - std::filesystem::copy(sourcePath, destPath, std::filesystem::copy_options::directories_only); - - for (const auto entry : std::filesystem::recursive_directory_iterator(sourcePath)) - { - const auto relativePath = std::filesystem::relative(entry.path(), sourcePath); - const auto fullDestPath = destPath / relativePath; - - if (!entry.is_regular_file()) - continue; - - // FIXME: kind of an awful hack, shouldn't just have this hardcoded like this - if (entry.path().filename() == "settings.json") - continue; - - std::filesystem::copy_file(entry.path(), fullDestPath, - std::filesystem::copy_options::overwrite_existing); - } - } - } - } - catch (const std::exception& e) - { - LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to initialize config system"); - } - - bool ConfigSystem::CheckIsPortable() const try - { - DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Current mutable data path: {}", GetMutableDataDir()); - const auto shippedCfgPath = GetMutableDataDir() / "cfg"; - - if (!std::filesystem::exists(shippedCfgPath)) - { - LogFatalError(MH_SOURCE_LOCATION_CURRENT(), - "The folder {} is missing or invalid.\nIt contains required files for this program.", shippedCfgPath); - } - - const auto nonPortablePath = shippedCfgPath / NON_PORTABLE_MARKER; - if (std::filesystem::exists(nonPortablePath)) - { - DebugLog("Installation is non-portable because marker file exists ({})", nonPortablePath); - return true; - } - else - { - DebugLog("Installation is portable because marker file does not exist ({})", nonPortablePath); - return false; - } - - return true; - } - catch (const std::exception& e) - { - LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to determine install type (installed/portable)"); - } - - std::filesystem::path ConfigSystem::CreateAppDataPath() const - { - return Platform::GetAppDataDir() / "TF2 Bot Detector"; - } - - std::filesystem::path ConfigSystem::ChooseMutableDataPath() const try - { - // Check the working directory for a cfg folder - auto mutableDir = m_WorkingDir; - if (!std::filesystem::exists(mutableDir / "cfg")) - mutableDir = m_ExeDir; - - if (!std::filesystem::exists(mutableDir / "cfg")) - { - LogFatalError(MH_SOURCE_LOCATION_CURRENT(), - "\"cfg\" folder not found in either the exe directory or working directory"); - } - - return mutableDir; - } - catch (const std::exception& e) - { - LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to choose mutable data path"); - } - - static ConfigSystem& GetConfigSystem() - { - static ConfigSystem s_ConfigSystem; - return s_ConfigSystem; - } -} - -const std::filesystem::path& tf2_bot_detector::GetMutableDataPath() -{ - return GetConfigSystem().GetMutableDataDir(); -} - auto tf2_bot_detector::GetConfigFilePaths(const std::string_view& basename) -> ConfigFilePaths { ConfigFilePaths retVal; - const std::filesystem::path cfg = GetMutableDataPath() / "cfg"; + const std::filesystem::path cfg("cfg"); if (std::filesystem::is_directory(cfg)) { try diff --git a/tf2_bot_detector/Config/ConfigHelpers.h b/tf2_bot_detector/Config/ConfigHelpers.h index d89d89eb..f4a44688 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.h +++ b/tf2_bot_detector/Config/ConfigHelpers.h @@ -23,10 +23,6 @@ namespace tf2_bot_detector COUNT, }; - // If this is a portable install, the application directory - // If this is a non-portable install, %APPDATA%/TF2 Bot Detector - const std::filesystem::path& GetMutableDataPath(); - struct ConfigFilePaths { std::filesystem::path m_User; @@ -184,11 +180,11 @@ namespace tf2_bot_detector const T* defaultMutableList = GetDefaultMutableList(); const T* localList = GetLocalList(); if (localList) - localList->SaveFile(GetMutableDataPath() / "cfg" / mh::format("{}.json", GetBaseFileName())); + localList->SaveFile(std::filesystem::path("cfg") / mh::format("{}.json", GetBaseFileName())); if (defaultMutableList && defaultMutableList != localList) { - const auto filename = GetMutableDataPath() / "cfg" / mh::format("{}.official.json", GetBaseFileName()); + const auto filename = std::filesystem::path("cfg") / mh::format("{}.official.json", GetBaseFileName()); if (!IsOfficial()) throw std::runtime_error("Attempted to save non-official data to "s << filename); diff --git a/tf2_bot_detector/Config/Settings.cpp b/tf2_bot_detector/Config/Settings.cpp index bbea8773..50b9bb30 100644 --- a/tf2_bot_detector/Config/Settings.cpp +++ b/tf2_bot_detector/Config/Settings.cpp @@ -1,6 +1,7 @@ #include "Settings.h" #include "Util/JSONUtils.h" #include "Util/PathUtils.h" +#include "Filesystem.h" #include "IPlayer.h" #include "Log.h" #include "PlayerListJSON.h" @@ -22,10 +23,9 @@ using namespace tf2_bot_detector; using namespace std::string_literals; using namespace std::string_view_literals; -static const std::filesystem::path& GetSettingsPath() +static std::filesystem::path GetSettingsPath(PathUsage usage) { - static const auto s_SettingsPath = tf2_bot_detector::GetMutableDataPath() / "cfg" / "settings.json"; - return s_SettingsPath; + return IFilesystem::Get().ResolvePath("cfg/settings.json", usage); } namespace tf2_bot_detector @@ -185,7 +185,7 @@ void Settings::LoadFile() { nlohmann::json json; { - const auto& settingsPath = GetSettingsPath(); + const auto settingsPath = GetSettingsPath(PathUsage::Read); std::ifstream file(settingsPath); if (!file.good()) { @@ -295,7 +295,7 @@ bool Settings::SaveFile() const try // Make sure we successfully serialize BEFORE we destroy our file auto jsonString = json.dump(1, '\t', true); { - const auto& settingsPath = GetSettingsPath(); + const auto settingsPath = GetSettingsPath(PathUsage::Write); std::filesystem::create_directories(std::filesystem::path(settingsPath).remove_filename()); std::ofstream file(settingsPath, std::ios::binary); if (!file.good()) diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp new file mode 100644 index 00000000..3cea773c --- /dev/null +++ b/tf2_bot_detector/Filesystem.cpp @@ -0,0 +1,179 @@ +#include "Filesystem.h" +#include "Log.h" +#include "Platform/Platform.h" + +#include +#include + +#include + +using namespace tf2_bot_detector; + +namespace +{ + class Filesystem final : public IFilesystem + { + public: + Filesystem(); + + struct FileDeleter + { + void operator()(FILE* file) const + { + fclose(file); + } + }; + + std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const override; + std::vector ReadFile(std::filesystem::path path) const override; + void WriteFile(std::filesystem::path path, const void* begin, const void* end) const override; + + private: + static constexpr char NON_PORTABLE_MARKER[] = ".non_portable"; + std::vector m_SearchPaths; + bool m_IsPortable; + + bool CheckIsPortable() const; + std::filesystem::path CreateAppDataPath() const; + std::filesystem::path ChooseMutableDataPath() const; + std::vector CreateSearchPaths() const; + + const std::filesystem::path m_ExeDir = Platform::GetCurrentExeDir(); + const std::filesystem::path m_WorkingDir = std::filesystem::current_path(); + const std::filesystem::path m_AppDataDir = Platform::GetAppDataDir() / "TF2 Bot Detector"; + //std::filesystem::path m_MutableDataDir = ChooseMutableDataPath(); + }; +} + +IFilesystem& IFilesystem::Get() +{ + static Filesystem s_Filesystem; + return s_Filesystem; +} + +Filesystem::Filesystem() try +{ + DebugLog("Initializing filesystem..."); + + m_SearchPaths.insert(m_SearchPaths.begin(), m_ExeDir); + + if (m_WorkingDir != m_ExeDir) + m_SearchPaths.insert(m_SearchPaths.begin(), m_WorkingDir); + + if (!ResolvePath(std::filesystem::path("cfg") / NON_PORTABLE_MARKER, PathUsage::Read).empty()) + { + m_SearchPaths.insert(m_SearchPaths.begin(), m_AppDataDir); + m_IsPortable = false; + } + else + { + m_IsPortable = true; + } + + { + std::string initMsg = "Filesystem initialized. Search paths:"; + for (const auto& searchPath : m_SearchPaths) + initMsg << "\n\t" << searchPath; + + DebugLog(std::move(initMsg)); + } +} +catch (const std::exception& e) +{ + LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to initialize filesystem"); +} + +std::filesystem::path Filesystem::ResolvePath(const std::filesystem::path& path, PathUsage usage) const +{ + if (path.is_absolute()) + return path; + + if (usage == PathUsage::Read) + { + for (const auto& searchPath : m_SearchPaths) + { + auto fullPath = searchPath / path; + if (std::filesystem::exists(fullPath)) + { + assert(fullPath.is_absolute()); + return fullPath; + } + } + + DebugLogWarning("Unable to find {} in any search path", path); + return {}; + } + else if (usage == PathUsage::Write) + { + if (!m_IsPortable) + return m_AppDataDir / path; + + return m_WorkingDir / path; + } + else + { + throw std::invalid_argument(mh::format("{}: Unknown PathUsage value {}", __FUNCTION__, int(usage))); + } +} + +std::vector Filesystem::ReadFile(std::filesystem::path path) const +{ + path = ResolvePath(path, PathUsage::Read); + if (path.empty()) + return {}; + + std::ifstream file(path, std::ios::binary); + if (!file.good()) + throw std::runtime_error(mh::format("{}: Failed to open file {}", __FUNCTION__, path)); + + file.seekg(0, std::ios::end); + if (!file.good()) + throw std::runtime_error(mh::format("{}: Failed to seek to end of {}", __FUNCTION__, path)); + + const auto length = file.tellg(); + file.seekg(0, std::ios::beg); + if (!file.good()) + throw std::runtime_error(mh::format("{}: Failed to seek to beginning of {}", __FUNCTION__, path)); + + std::vector retVal; + retVal.resize(length); + file.read(reinterpret_cast(retVal.data()), length); + if (!file.good()) + throw std::runtime_error(mh::format("{}: Failed to read file {}", __FUNCTION__, path)); + + return retVal; +} + +void Filesystem::WriteFile(std::filesystem::path path, const void* begin, const void* end) const +{ + path = ResolvePath(path, PathUsage::Write); + + std::ofstream file(path, std::ios::binary | std::ios::trunc); + if (!file.good()) + throw std::runtime_error(mh::format("{}: Failed to open file {}", __FUNCTION__, path)); + + const auto bytes = uintptr_t(end) - uintptr_t(begin); + file.write(reinterpret_cast(begin), bytes); + if (!file.good()) + throw std::runtime_error(mh::format("{}: Failed to write {} bytes to {}", __FUNCTION__, bytes, path)); +} + +std::vector Filesystem::CreateSearchPaths() const try +{ + std::vector retVal; + + const auto exeDir = Platform::GetCurrentExeDir(); + retVal.insert(retVal.begin(), exeDir); + + if (auto workingDir = std::filesystem::current_path(); workingDir != exeDir) + retVal.insert(retVal.begin(), std::move(workingDir)); + + if (!ResolvePath(std::filesystem::path("cfg") / NON_PORTABLE_MARKER, PathUsage::Read).empty()) + retVal.insert(retVal.begin(), Platform::GetAppDataDir() / "TF2 Bot Detector"); + + return retVal; +} +catch (const std::exception& e) +{ + LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e); +} diff --git a/tf2_bot_detector/Filesystem.h b/tf2_bot_detector/Filesystem.h new file mode 100644 index 00000000..111e80a0 --- /dev/null +++ b/tf2_bot_detector/Filesystem.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace tf2_bot_detector +{ + enum class PathUsage + { + Read, + Write, + }; + + class IFilesystem + { + public: + virtual ~IFilesystem() = default; + + static IFilesystem& Get(); + + virtual std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const = 0; + + //virtual std::fstream OpenFile(const std::filesystem::path& path) = 0; + virtual std::vector ReadFile(std::filesystem::path path) const = 0; + virtual void WriteFile(std::filesystem::path path, const void* begin, const void* end) const = 0; + + void WriteFile(const std::filesystem::path& path, const std::string_view& data) + { + return WriteFile(path, data.data(), data.data() + data.size()); + } + }; +} From f4913c6a168f397065fa324ae33fcb18b7900d59 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 17:50:43 -0700 Subject: [PATCH 029/161] Fixed writing to local directories from ConfigHelpers.cpp. --- tf2_bot_detector/Config/ConfigHelpers.cpp | 40 ++++++----------- tf2_bot_detector/Filesystem.cpp | 52 +++++++++++++---------- tf2_bot_detector/Filesystem.h | 25 ++++++++++- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/tf2_bot_detector/Config/ConfigHelpers.cpp b/tf2_bot_detector/Config/ConfigHelpers.cpp index df29f6aa..4e1415d8 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.cpp +++ b/tf2_bot_detector/Config/ConfigHelpers.cpp @@ -3,6 +3,7 @@ #include "Platform/Platform.h" #include "Util/JSONUtils.h" #include "Util/RegexUtils.h" +#include "Filesystem.h" #include "Log.h" #include "Version.h" @@ -10,7 +11,6 @@ #include #include -#include #include using namespace std::string_literals; @@ -53,15 +53,7 @@ auto tf2_bot_detector::GetConfigFilePaths(const std::string_view& basename) -> C static void SaveJSONToFile(const std::filesystem::path& filename, const nlohmann::json& json) { - auto jsonString = json.dump(1, '\t', true); - - std::ofstream file(filename, std::ios::binary); - if (!file.good()) - throw std::runtime_error("Failed to open file for writing"); - - file << jsonString << '\n'; - if (!file.good()) - throw std::runtime_error("Failed to write json to file"); + IFilesystem::Get().WriteFile(filename, json.dump(1, '\t', true) << '\n'); } static ConfigSchemaInfo LoadAndValidateSchema(const ConfigFileBase& config, const nlohmann::json& json) @@ -78,16 +70,6 @@ static ConfigSchemaInfo LoadAndValidateSchema(const ConfigFileBase& config, cons static bool TryAutoUpdate(std::filesystem::path filename, const nlohmann::json& existingJson, SharedConfigFileBase& config, const HTTPClient& client) { - try - { - filename = std::filesystem::absolute(filename); - } - catch (const std::exception& e) - { - LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to convert {} to absolute path", filename); - return false; - } - auto fileInfoJson = existingJson.find("file_info"); if (fileInfoJson == existingJson.end()) { @@ -222,22 +204,26 @@ bool ConfigFileBase::LoadFileInternal(const std::filesystem::path& filename, con nlohmann::json json; { - std::ifstream file(filename); - if (!file.good()) + Log("Loading {}...", filename); + + std::string file; + try { - DebugLog("Failed to open {}", filename); + file = IFilesystem::Get().ReadFile(filename); + } + catch (const std::exception& e) + { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to load {}", filename); return false; } - Log("Loading {}...", filename); - try { - file >> json; + json = nlohmann::json::parse(file); } catch (const std::exception& e) { - LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Exception when parsing JSON from {}", filename); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to parse JSON from {}", filename); return false; } } diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp index 3cea773c..a2585a0e 100644 --- a/tf2_bot_detector/Filesystem.cpp +++ b/tf2_bot_detector/Filesystem.cpp @@ -25,7 +25,7 @@ namespace }; std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const override; - std::vector ReadFile(std::filesystem::path path) const override; + std::string ReadFile(std::filesystem::path path) const override; void WriteFile(std::filesystem::path path, const void* begin, const void* end) const override; private: @@ -88,35 +88,41 @@ std::filesystem::path Filesystem::ResolvePath(const std::filesystem::path& path, if (path.is_absolute()) return path; - if (usage == PathUsage::Read) + auto retVal = std::invoke([&]() -> std::filesystem::path { - for (const auto& searchPath : m_SearchPaths) + if (usage == PathUsage::Read) { - auto fullPath = searchPath / path; - if (std::filesystem::exists(fullPath)) + for (const auto& searchPath : m_SearchPaths) { - assert(fullPath.is_absolute()); - return fullPath; + auto fullPath = searchPath / path; + if (std::filesystem::exists(fullPath)) + { + assert(fullPath.is_absolute()); + return fullPath; + } } + + DebugLogWarning("Unable to find {} in any search path", path); + return {}; } + else if (usage == PathUsage::Write) + { + if (!m_IsPortable) + return m_AppDataDir / path; - DebugLogWarning("Unable to find {} in any search path", path); - return {}; - } - else if (usage == PathUsage::Write) - { - if (!m_IsPortable) - return m_AppDataDir / path; + return m_WorkingDir / path; + } + else + { + throw std::invalid_argument(mh::format("{}: Unknown PathUsage value {}", __FUNCTION__, int(usage))); + } + }); - return m_WorkingDir / path; - } - else - { - throw std::invalid_argument(mh::format("{}: Unknown PathUsage value {}", __FUNCTION__, int(usage))); - } + DebugLog("ResolvePath({}, {}) -> {}", path, usage, retVal); + return std::move(retVal); } -std::vector Filesystem::ReadFile(std::filesystem::path path) const +std::string Filesystem::ReadFile(std::filesystem::path path) const { path = ResolvePath(path, PathUsage::Read); if (path.empty()) @@ -135,9 +141,9 @@ std::vector Filesystem::ReadFile(std::filesystem::path path) const if (!file.good()) throw std::runtime_error(mh::format("{}: Failed to seek to beginning of {}", __FUNCTION__, path)); - std::vector retVal; + std::string retVal; retVal.resize(length); - file.read(reinterpret_cast(retVal.data()), length); + file.read(retVal.data(), length); if (!file.good()) throw std::runtime_error(mh::format("{}: Failed to read file {}", __FUNCTION__, path)); diff --git a/tf2_bot_detector/Filesystem.h b/tf2_bot_detector/Filesystem.h index 111e80a0..b2b92450 100644 --- a/tf2_bot_detector/Filesystem.h +++ b/tf2_bot_detector/Filesystem.h @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace tf2_bot_detector @@ -24,12 +25,32 @@ namespace tf2_bot_detector virtual std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const = 0; //virtual std::fstream OpenFile(const std::filesystem::path& path) = 0; - virtual std::vector ReadFile(std::filesystem::path path) const = 0; + virtual std::string ReadFile(std::filesystem::path path) const = 0; virtual void WriteFile(std::filesystem::path path, const void* begin, const void* end) const = 0; - void WriteFile(const std::filesystem::path& path, const std::string_view& data) + void WriteFile(const std::filesystem::path& path, const std::string_view& data) const { return WriteFile(path, data.data(), data.data() + data.size()); } }; } + +template +std::basic_ostream& operator<<(std::basic_ostream& os, tf2_bot_detector::PathUsage usage) +{ + using namespace tf2_bot_detector; + +#undef OS_CASE +#define OS_CASE(x) case x : return os << #x + + switch (usage) + { + OS_CASE(PathUsage::Read); + OS_CASE(PathUsage::Write); + + default: + return os << "PathUsage(" << +std::underlying_type_t(usage) << ')'; + } + +#undef OS_CASE +} From a958c1a57207e5a8b707e1ed7bd4addfb90c0c3c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 20 Aug 2020 19:54:01 -0700 Subject: [PATCH 030/161] Only load third party config files from the mutable data directory --- tf2_bot_detector/Config/ConfigHelpers.cpp | 12 ++++++++++-- tf2_bot_detector/Filesystem.cpp | 24 ++++++++++++++++------- tf2_bot_detector/Filesystem.h | 6 ++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/tf2_bot_detector/Config/ConfigHelpers.cpp b/tf2_bot_detector/Config/ConfigHelpers.cpp index 4e1415d8..d4ebb5f2 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.cpp +++ b/tf2_bot_detector/Config/ConfigHelpers.cpp @@ -21,8 +21,16 @@ auto tf2_bot_detector::GetConfigFilePaths(const std::string_view& basename) -> C { ConfigFilePaths retVal; - const std::filesystem::path cfg("cfg"); - if (std::filesystem::is_directory(cfg)) + { + const std::filesystem::path cfg("cfg"); + if (auto found = IFilesystem::Get().ResolvePath(cfg / mh::format("{}.official.json", basename), PathUsage::Read); !found.empty()) + retVal.m_Official = found; + if (auto found = IFilesystem::Get().ResolvePath(cfg / mh::format("{}.json", basename), PathUsage::Read); !found.empty()) + retVal.m_User = found; + } + + if (const auto cfg = IFilesystem::Get().GetMutableDataDir() / std::filesystem::path("cfg"); + std::filesystem::is_directory(cfg)) { try { diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp index a2585a0e..0af601e6 100644 --- a/tf2_bot_detector/Filesystem.cpp +++ b/tf2_bot_detector/Filesystem.cpp @@ -16,18 +16,14 @@ namespace public: Filesystem(); - struct FileDeleter - { - void operator()(FILE* file) const - { - fclose(file); - } - }; + cppcoro::generator GetSearchPaths() const override; std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const override; std::string ReadFile(std::filesystem::path path) const override; void WriteFile(std::filesystem::path path, const void* begin, const void* end) const override; + std::filesystem::path GetMutableDataDir() const; + private: static constexpr char NON_PORTABLE_MARKER[] = ".non_portable"; std::vector m_SearchPaths; @@ -83,6 +79,12 @@ catch (const std::exception& e) LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to initialize filesystem"); } +cppcoro::generator Filesystem::GetSearchPaths() const +{ + for (auto searchPath : m_SearchPaths) + co_yield searchPath; +} + std::filesystem::path Filesystem::ResolvePath(const std::filesystem::path& path, PathUsage usage) const { if (path.is_absolute()) @@ -164,6 +166,14 @@ void Filesystem::WriteFile(std::filesystem::path path, const void* begin, const throw std::runtime_error(mh::format("{}: Failed to write {} bytes to {}", __FUNCTION__, bytes, path)); } +std::filesystem::path Filesystem::GetMutableDataDir() const +{ + if (m_IsPortable) + return m_WorkingDir; + else + return m_AppDataDir; +} + std::vector Filesystem::CreateSearchPaths() const try { std::vector retVal; diff --git a/tf2_bot_detector/Filesystem.h b/tf2_bot_detector/Filesystem.h index b2b92450..923399c2 100644 --- a/tf2_bot_detector/Filesystem.h +++ b/tf2_bot_detector/Filesystem.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -22,8 +24,12 @@ namespace tf2_bot_detector static IFilesystem& Get(); + virtual cppcoro::generator GetSearchPaths() const = 0; + virtual std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const = 0; + virtual std::filesystem::path GetMutableDataDir() const = 0; + //virtual std::fstream OpenFile(const std::filesystem::path& path) = 0; virtual std::string ReadFile(std::filesystem::path path) const = 0; virtual void WriteFile(std::filesystem::path path, const void* begin, const void* end) const = 0; From e99ea71f029de6e9efd1d66f0fb8e8e410aec756 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 12:23:54 -0700 Subject: [PATCH 031/161] Fixed debug report and logging directories. --- tf2_bot_detector/Filesystem.cpp | 36 ++++++--------------- tf2_bot_detector/Log.cpp | 52 +++++++++++++++++++----------- tf2_bot_detector/UI/MainWindow.cpp | 28 +++++++++------- 3 files changed, 59 insertions(+), 57 deletions(-) diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp index 0af601e6..62a684ff 100644 --- a/tf2_bot_detector/Filesystem.cpp +++ b/tf2_bot_detector/Filesystem.cpp @@ -29,11 +29,6 @@ namespace std::vector m_SearchPaths; bool m_IsPortable; - bool CheckIsPortable() const; - std::filesystem::path CreateAppDataPath() const; - std::filesystem::path ChooseMutableDataPath() const; - std::vector CreateSearchPaths() const; - const std::filesystem::path m_ExeDir = Platform::GetCurrentExeDir(); const std::filesystem::path m_WorkingDir = std::filesystem::current_path(); const std::filesystem::path m_AppDataDir = Platform::GetAppDataDir() / "TF2 Bot Detector"; @@ -49,7 +44,7 @@ IFilesystem& IFilesystem::Get() Filesystem::Filesystem() try { - DebugLog("Initializing filesystem..."); + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Initializing filesystem..."); m_SearchPaths.insert(m_SearchPaths.begin(), m_ExeDir); @@ -58,11 +53,20 @@ Filesystem::Filesystem() try if (!ResolvePath(std::filesystem::path("cfg") / NON_PORTABLE_MARKER, PathUsage::Read).empty()) { + DebugLog("Installation detected as non-portable."); m_SearchPaths.insert(m_SearchPaths.begin(), m_AppDataDir); m_IsPortable = false; + + // If we crash, we want our working directory to be somewhere we can write to. + if (std::filesystem::create_directories(m_AppDataDir)) + DebugLog("Created {}", m_AppDataDir); + + std::filesystem::current_path(m_AppDataDir); + DebugLog("Set working directory to {}", m_AppDataDir); } else { + DebugLog("Installation detected as portable."); m_IsPortable = true; } @@ -173,23 +177,3 @@ std::filesystem::path Filesystem::GetMutableDataDir() const else return m_AppDataDir; } - -std::vector Filesystem::CreateSearchPaths() const try -{ - std::vector retVal; - - const auto exeDir = Platform::GetCurrentExeDir(); - retVal.insert(retVal.begin(), exeDir); - - if (auto workingDir = std::filesystem::current_path(); workingDir != exeDir) - retVal.insert(retVal.begin(), std::move(workingDir)); - - if (!ResolvePath(std::filesystem::path("cfg") / NON_PORTABLE_MARKER, PathUsage::Read).empty()) - retVal.insert(retVal.begin(), Platform::GetAppDataDir() / "TF2 Bot Detector"); - - return retVal; -} -catch (const std::exception& e) -{ - LogFatalException(MH_SOURCE_LOCATION_CURRENT(), e); -} diff --git a/tf2_bot_detector/Log.cpp b/tf2_bot_detector/Log.cpp index 69b65fdc..555f0c4f 100644 --- a/tf2_bot_detector/Log.cpp +++ b/tf2_bot_detector/Log.cpp @@ -1,8 +1,10 @@ #include "Log.h" #include "Util/PathUtils.h" +#include "Filesystem.h" #include #include +#include #include #include #include @@ -34,6 +36,8 @@ namespace LogManager(const LogManager&) = delete; LogManager& operator=(const LogManager&) = delete; + void Init(); + void Log(std::string msg, const LogMessageColor& color, LogSeverity severity, LogVisibility visibility = LogVisibility::Default, time_point_t timestamp = clock_t::now()) override; void LogToStream(std::string msg, std::ostream& output, time_point_t timestamp = clock_t::now(), bool skipScrub = false) const; @@ -73,7 +77,18 @@ namespace static LogManager& GetLogState() { - static LogManager s_LogState; + // We need this song and dance because this initialization might be reentrant + // (filesystem might print log messages) + bool isFirstInit = false; + static LogManager s_LogState = [&]() + { + isFirstInit = true; + return LogManager{}; + }(); + + if (isFirstInit) + s_LogState.Init(); + return s_LogState; } } @@ -89,23 +104,30 @@ static constexpr LogMessageColor COLOR_ERROR = { 1, 0.25, 0, 1 }; LogManager::LogManager() { +} + +void LogManager::Init() +{ + ::DebugLog(MH_SOURCE_LOCATION_CURRENT()); + std::lock_guard lock(m_LogMutex); + const auto t = ToTM(clock_t::now()); - const auto timestampStr = mh::format("{}", std::put_time(&t, "%Y-%m-%d_%H-%M-%S")); + const mh::fmtstr<128> timestampStr("{}", std::put_time(&t, "%Y-%m-%d_%H-%M-%S")); // Pick file name { - std::filesystem::path logPath = "logs"; + std::filesystem::path logPath = IFilesystem::Get().ResolvePath("logs", PathUsage::Write); std::error_code ec; std::filesystem::create_directories(logPath, ec); if (ec) { - this->Log(mh::format("Failed to create directory {}. Log output will go to stdout.", logPath), - COLOR_WARNING, LogSeverity::Warning); + ::LogWarning("Failed to create one or more directory in the path {}. Log output will go to stdout.", + logPath); } else { - m_FileName = logPath / mh::format("{}.log", timestampStr); + m_FileName = logPath / mh::fmtstr<128>("{}.log", timestampStr).view(); } } @@ -114,10 +136,7 @@ LogManager::LogManager() { m_File = std::ofstream(m_FileName, std::ofstream::ate | std::ofstream::app | std::ofstream::out | std::ofstream::binary); if (!m_File.good()) - { - this->Log(mh::format("Failed to open log file {}. Log output will go to stdout only.", m_FileName), - COLOR_WARNING, LogSeverity::Warning); - } + ::LogWarning("Failed to open log file {}. Log output will go to stdout only.", m_FileName); } { @@ -126,20 +145,15 @@ LogManager::LogManager() std::filesystem::create_directories(logDir, ec); if (ec) { - this->Log( - mh::format("Failed to create one or more directory in the path {}. Console output will not be logged.", logDir), - COLOR_WARNING, LogSeverity::Warning); + ::LogWarning("Failed to create one or more directory in the path {}. Console output will not be logged.", + logDir); } else { - auto logPath = logDir / mh::format("console_{}.log", timestampStr); + auto logPath = logDir / mh::fmtstr<128>("console_{}.log", timestampStr).view(); m_ConsoleLogFile = std::ofstream(logPath, std::ofstream::ate | std::ofstream::binary); if (!m_ConsoleLogFile.good()) - { - this->Log( - mh::format("Failed to open console log file {}. Console output will not be logged.", logPath), - COLOR_WARNING, LogSeverity::Warning); - } + ::LogWarning("Failed to open console log file {}. Console output will not be logged.", logPath); } } } diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index facb6fae..cabe36f3 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -8,6 +8,7 @@ #include "ImGui_TF2BotDetector.h" #include "Actions/ActionGenerators.h" #include "BaseTextures.h" +#include "Filesystem.h" #include "Log.h" #include "IPlayer.h" #include "TextureManager.h" @@ -509,30 +510,28 @@ void MainWindow::OnDrawAboutPopup() } } -void MainWindow::GenerateDebugReport() +void MainWindow::GenerateDebugReport() try { Log("Generating debug_report.zip..."); + const auto dbgReportLocation = IFilesystem::Get().ResolvePath("debug_report.zip", PathUsage::Write); + { using namespace libzippp; - ZipArchive archive("debug_report.zip"); + ZipArchive archive(dbgReportLocation.string()); archive.open(ZipArchive::NEW); - if (!archive.addFile("console.log", (m_Settings.GetTFDir() / "console.log").string())) - { - LogError("Failed to add console.log to debug report"); - } - - for (const auto& entry : std::filesystem::recursive_directory_iterator("logs")) + const auto mutableDir = IFilesystem::Get().GetMutableDataDir(); + for (const auto& entry : std::filesystem::recursive_directory_iterator(mutableDir / "logs")) { if (!entry.is_regular_file()) continue; const auto& path = entry.path(); - if (archive.addFile(path.string(), path.string())) + if (archive.addFile(std::filesystem::relative(path, mutableDir).string(), path.string())) Log("Added file to debug report: {}", path); else - Log("Failed to add file to debug report: {}", path); + LogWarning("Failed to add file to debug report: {}", path); } if (auto err = archive.close(); err != LIBZIPPP_OK) @@ -541,8 +540,13 @@ void MainWindow::GenerateDebugReport() return; } } - Log("Finished generating debug_report.zip."); - Shell::ExploreToAndSelect("debug_report.zip"); + + Log("Finished generating debug_report.zip ({})", dbgReportLocation); + Shell::ExploreToAndSelect(dbgReportLocation); +} +catch (const std::exception& e) +{ + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Failed to generate debug_report.zip"); } void MainWindow::OnDrawServerStats() From 101873a4373e9d3d681d6bf02a67a25fbfa8b20c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 12:51:21 -0700 Subject: [PATCH 032/161] Create both debug and release builds --- .github/workflows/ccpp.yml | 41 ++++++++++++++++++++------- msix/package_staging/AppxManifest.xml | 4 +-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 47830a03..0abaee69 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -16,6 +16,7 @@ jobs: os: [windows-latest] triplet: [x86-windows, x64-windows] discord_integration: [true, false] + build_type: [Debug, Release] include: - os: windows-latest triplet: x86-windows @@ -61,6 +62,7 @@ jobs: echo "matrix.discord_integration = ${{ matrix.discord_integration }}" echo "matrix.tf2bd_arch = ${{ matrix.tf2bd_arch }}" echo "matrix.build_arch = ${{ matrix.build_arch }}" + echo "matrix.build_type = ${{ matrix.build_type }}" echo "env.TF2BD_ENABLE_ARTIFACT_UPLOAD = ${{ env.TF2BD_ENABLE_ARTIFACT_UPLOAD }}" echo "steps.tf2bd_paths.outputs.workspace = ${{ steps.tf2bd_paths.outputs.workspace }}" echo "steps.tf2bd_paths.outputs.build_dir = ${{ steps.tf2bd_paths.outputs.build_dir }}" @@ -86,8 +88,8 @@ jobs: run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -A${{ matrix.build_arch }} -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ - cmake --build . --config Release + cmake -A${{ matrix.build_arch }} -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ + cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts if: ${{ startsWith(matrix.os, 'windows') }} @@ -101,7 +103,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: - name: "smartscreen_${{ matrix.triplet }}" + name: "smartscreen_${{ matrix.triplet }}_${{ matrix.build_type }}" path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" @@ -124,7 +126,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: - name: "tf2_bot_detector_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}" + name: "tf2_bot_detector_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" if-no-files-found: error path: ${{ steps.tf2bd_paths.outputs.workspace }}/staging/ @@ -132,7 +134,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: - name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}" + name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.pdb" @@ -145,6 +147,10 @@ jobs: fail-fast: false matrix: tf2bd_arch: [x86, x64] + build_type: + - name: Debug + suffix: -debug + - name: Release steps: - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME @@ -157,8 +163,14 @@ jobs: if: ${{ !startsWith(github.ref, 'refs/tags/') }} run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" + - name: Debug + run: | + echo "matrix.tf2bd_arch = ${{ matrix.tf2bd_arch }}" + echo "matrix.build_type.name = ${{ matrix.build_type.name }}" + echo "matrix.build_type.suffix = ${{ matrix.build_type.suffix }}" + - name: Config msix filename - run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}.msix" + run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" - name: Configure PATH for windows sdk uses: ilammy/msvc-dev-cmd@v1 @@ -169,19 +181,21 @@ jobs: - name: Download artifact uses: actions/download-artifact@v2 with: - name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}" + name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}" path: msix/package_staging/app - name: Mark as non-portable shell: bash run: touch msix/package_staging/app/cfg/.non_portable - - name: Find and replace data in package Config + - name: Find and replace data in package config shell: bash run: | cat msix/package_staging/AppxManifest.xml \ | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ | sed 's/TF2BD_BUILD_NUMBER_REPLACE_ME/${{ github.run_number }}/g' \ + | sed 's/TF2BD_IDENTITY_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ + | sed 's/TF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ | tee msix/package_staging/AppxManifest.xml - name: Create msix package @@ -207,6 +221,13 @@ jobs: msixbundle: runs-on: windows-latest needs: msix + strategy: + fail-fast: false + matrix: + build_type: + - name: Debug + suffix: -debug + - name: Release steps: - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME @@ -225,12 +246,12 @@ jobs: - name: Download msix (x86) uses: actions/download-artifact@v2 with: - name: "tf2_bot_detector_x86-windows_${{ env.TF2BD_VERSION }}.msix" + name: "tf2_bot_detector_x86-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" path: msix/bundle_staging - name: Download msix (x64) uses: actions/download-artifact@v2 with: - name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}.msix" + name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" path: msix/bundle_staging - name: Create bundle diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index af72bb0b..71d49809 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -2,9 +2,9 @@ - + - TF2 Bot Detector + TF2 Bot DetectorTF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME pazer Automatically detects and votekicks cheaters/bots in TF2 casual. logo_256.png From 6b2eff523b6bd3a51dbb90309228c55f7efbd938 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 13:00:46 -0700 Subject: [PATCH 033/161] Fix the debug/release output directory --- .github/workflows/ccpp.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 0abaee69..cbdf9acd 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -11,7 +11,7 @@ jobs: ci: runs-on: ${{ matrix.os }} strategy: - fail-fast: false + # fail-fast: false matrix: os: [windows-latest] triplet: [x86-windows, x64-windows] @@ -88,7 +88,15 @@ jobs: run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -A${{ matrix.build_arch }} -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ + cmake -A${{ matrix.build_arch }} \ + -DTF2BD_IS_CI_COMPILE=ON \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ + -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ + ../ cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts @@ -144,7 +152,7 @@ jobs: runs-on: windows-latest needs: ci strategy: - fail-fast: false + # fail-fast: false matrix: tf2bd_arch: [x86, x64] build_type: @@ -222,7 +230,7 @@ jobs: runs-on: windows-latest needs: msix strategy: - fail-fast: false + # fail-fast: false matrix: build_type: - name: Debug From e31cb0d79bb67fe9cfef12144058837a20c53e98 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 14:16:21 -0700 Subject: [PATCH 034/161] Only resolve the version once --- .github/workflows/ccpp.yml | 147 ++++++++++++++++++-------------- CMakeLists.txt | 7 +- tf2_bot_detector/Version.base.h | 7 +- 3 files changed, 94 insertions(+), 67 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index cbdf9acd..20891456 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -8,7 +8,46 @@ on: - '*.md' jobs: - ci: + # setup_vcpkg: + # runs-on: ${{ matrix.platform.os }} + # strategy: + # fail-fast: true + # matrix: + # platform: + # - os: windows-latest + # triplet: x64-windows + # - os: windows-latest + # triplet: x86-windows + # steps: + # - name: Checkout + # uses: actions/checkout@v2 + # with: + # submodules: recursive + + setup_version: + # needs: setup_vcpkg + runs-on: windows-latest + steps: + # - name: Checkout + # uses: actions/checkout@v2 + # with: + # submodules: recursive + - name: Extract TF2BD_VERSION + shell: bash + run: | + mkdir build_dir + cd build_dir + cmake ../ + TF2BD_VERSION=`cat ${{ steps.tf2bd_paths.outputs.build_dir }}/CMakeCache.txt | grep CMAKE_PROJECT_VERSION: | cut -d "=" -f2` + echo "::set-env name=TF2BD_VERSION::$TF2BD_VERSION.${{ github.run_number }}" + - name: Store TF2BD_VERSION + uses: nick-invision/persist-action-data@v1 + with: + data: ${{ env.TF2BD_VERSION }} + variable: TF2BD_VERSION + + build: + needs: setup_version runs-on: ${{ matrix.os }} strategy: # fail-fast: false @@ -28,16 +67,10 @@ jobs: build_arch: x64 steps: - - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME - - uses: benjlevesque/short-sha@v1.1 - id: short_sha - - name: Config TF2BD_VERSION (tag) - if: startsWith(github.ref, 'refs/tags/') - run: echo "::set-env name=TF2BD_VERSION::${{ env.GIT_TAG_NAME }}" - - name: Config TF2BD_VERSION (SHA) - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" - + - name: Retrieve TF2BD_VERSION + uses: nick-invision/persist-action-data@v1 + with: + retrieve_variables: TF2BD_VERSION - name: Determine artifact behavior if: matrix.discord_integration == true run: echo "::set-env name=TF2BD_ENABLE_ARTIFACT_UPLOAD::1" @@ -150,7 +183,7 @@ jobs: msix: runs-on: windows-latest - needs: ci + needs: build strategy: # fail-fast: false matrix: @@ -161,15 +194,10 @@ jobs: - name: Release steps: - - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME - - uses: benjlevesque/short-sha@v1.1 - id: short_sha - - name: Config TF2BD_VERSION (tag) - if: startsWith(github.ref, 'refs/tags/') - run: echo "::set-env name=TF2BD_VERSION::${{ env.GIT_TAG_NAME }}" - - name: Config TF2BD_VERSION (SHA) - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" + - name: Retrieve TF2BD_VERSION + uses: nick-invision/persist-action-data@v1 + with: + retrieve_variables: TF2BD_VERSION - name: Debug run: | @@ -238,45 +266,40 @@ jobs: - name: Release steps: - - uses: olegtarasov/get-tag@v2 # GIT_TAG_NAME - - uses: benjlevesque/short-sha@v1.1 - id: short_sha - - name: Config TF2BD_VERSION (tag) - if: startsWith(github.ref, 'refs/tags/') - run: echo "::set-env name=TF2BD_VERSION::${{ env.GIT_TAG_NAME }}" - - name: Config TF2BD_VERSION (SHA) - if: ${{ !startsWith(github.ref, 'refs/tags/') }} - run: echo "::set-env name=TF2BD_VERSION::${{ steps.short_sha.outputs.sha }}" - - - name: Configure PATH for windows sdk - uses: ilammy/msvc-dev-cmd@v1 - - - name: Download msix (x86) - uses: actions/download-artifact@v2 - with: - name: "tf2_bot_detector_x86-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" - path: msix/bundle_staging - - name: Download msix (x64) - uses: actions/download-artifact@v2 - with: - name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" - path: msix/bundle_staging - - - name: Create bundle - run: | - cd msix - makeappx bundle /d bundle_staging /bv 1.1.0.11 /p tf2_bot_detector.msixbundle - - - name: Sign bundle - uses: PazerOP/code-sign-action@v3 - with: - folder: msix - certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' - password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - - - name: Upload bundle - uses: actions/upload-artifact@v2 - with: - name: tf2_bot_detector.msixbundle - path: "msix/tf2_bot_detector.msixbundle" + - name: Retrieve TF2BD_VERSION + uses: nick-invision/persist-action-data@v1 + with: + retrieve_variables: TF2BD_VERSION + + - name: Configure PATH for windows sdk + uses: ilammy/msvc-dev-cmd@v1 + + - name: Download msix (x86) + uses: actions/download-artifact@v2 + with: + name: "tf2_bot_detector_x86-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" + path: msix/bundle_staging + - name: Download msix (x64) + uses: actions/download-artifact@v2 + with: + name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" + path: msix/bundle_staging + + - name: Create bundle + run: | + cd msix + makeappx bundle /d bundle_staging /bv 1.1.0.11 /p tf2_bot_detector.msixbundle + + - name: Sign bundle + uses: PazerOP/code-sign-action@v3 + with: + folder: msix + certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' + password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + + - name: Upload bundle + uses: actions/upload-artifact@v2 + with: + name: tf2_bot_detector.msixbundle + path: "msix/tf2_bot_detector.msixbundle" diff --git a/CMakeLists.txt b/CMakeLists.txt index 11ac2d4d..13f56b28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,15 @@ cmake_minimum_required(VERSION 3.17.2) -project(tf2_bot_detector VERSION 1.1.0.12) +project(tf2_bot_detector VERSION 1.1.0) include(GenerateExportHeader) message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") + +# We use this as the build number. message("TF2BD CMAKE_PROJECT_VERSION_TWEAK = ${CMAKE_PROJECT_VERSION_TWEAK}") +if ("${CMAKE_PROJECT_VERSION_TWEAK}" STREQUAL "") + set(CMAKE_PROJECT_VERSION_TWEAK 0) +endif() option(TF2BD_IS_CI_COMPILE "Set to true if this is a compile on a CI service. Used to help determine if user has made modifications to the source code." off) if (TF2BD_IS_CI_COMPILE) diff --git a/tf2_bot_detector/Version.base.h b/tf2_bot_detector/Version.base.h index 31f03a4d..70eebb7c 100644 --- a/tf2_bot_detector/Version.base.h +++ b/tf2_bot_detector/Version.base.h @@ -12,21 +12,20 @@ namespace tf2_bot_detector int m_Major; int m_Minor; int m_Patch; - int m_Preview; + int m_Build; }; - static constexpr char VERSION_STRING[] = "${CMAKE_PROJECT_VERSION}"; static constexpr Version VERSION = { .m_Major = ${CMAKE_PROJECT_VERSION_MAJOR}, .m_Minor = ${CMAKE_PROJECT_VERSION_MINOR}, .m_Patch = ${CMAKE_PROJECT_VERSION_PATCH}, - .m_Preview = ${CMAKE_PROJECT_VERSION_TWEAK}, + .m_Build = ${CMAKE_PROJECT_VERSION_TWEAK}, }; } template std::basic_ostream& operator<<(std::basic_ostream& os, const tf2_bot_detector::Version& v) { - return os << v.m_Major << '.' << v.m_Minor << '.' << v.m_Patch << '.' << v.m_Preview; + return os << v.m_Major << '.' << v.m_Minor << '.' << v.m_Patch << '.' << v.m_Build; } From c19d4a2ae723ebc1fdb6d5d21d7b51d7d88d9038 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 14:18:50 -0700 Subject: [PATCH 035/161] Fixed tf2bd_version extraction --- .github/workflows/ccpp.yml | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 20891456..f0698c58 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -8,44 +8,29 @@ on: - '*.md' jobs: - # setup_vcpkg: - # runs-on: ${{ matrix.platform.os }} - # strategy: - # fail-fast: true - # matrix: - # platform: - # - os: windows-latest - # triplet: x64-windows - # - os: windows-latest - # triplet: x86-windows - # steps: - # - name: Checkout - # uses: actions/checkout@v2 - # with: - # submodules: recursive - setup_version: - # needs: setup_vcpkg runs-on: windows-latest steps: - # - name: Checkout - # uses: actions/checkout@v2 - # with: - # submodules: recursive + - name: Checkout + uses: actions/checkout@v2 + - name: Extract TF2BD_VERSION shell: bash run: | mkdir build_dir cd build_dir - cmake ../ + cmake ../ || true # we know this will fail, we just need version info from CMakeCache.txt TF2BD_VERSION=`cat ${{ steps.tf2bd_paths.outputs.build_dir }}/CMakeCache.txt | grep CMAKE_PROJECT_VERSION: | cut -d "=" -f2` echo "::set-env name=TF2BD_VERSION::$TF2BD_VERSION.${{ github.run_number }}" + - name: Store TF2BD_VERSION uses: nick-invision/persist-action-data@v1 with: data: ${{ env.TF2BD_VERSION }} variable: TF2BD_VERSION + + build: needs: setup_version runs-on: ${{ matrix.os }} From b792b6183a0fe1b2763bdb219b1b8863e0338efb Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 14:20:23 -0700 Subject: [PATCH 036/161] Fixed tf2bd_version extraction --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f0698c58..4a0dba8d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -20,7 +20,7 @@ jobs: mkdir build_dir cd build_dir cmake ../ || true # we know this will fail, we just need version info from CMakeCache.txt - TF2BD_VERSION=`cat ${{ steps.tf2bd_paths.outputs.build_dir }}/CMakeCache.txt | grep CMAKE_PROJECT_VERSION: | cut -d "=" -f2` + TF2BD_VERSION=`cat CMakeCache.txt | grep CMAKE_PROJECT_VERSION: | cut -d "=" -f2` echo "::set-env name=TF2BD_VERSION::$TF2BD_VERSION.${{ github.run_number }}" - name: Store TF2BD_VERSION From bea04443ffe8a2468baabe0d96cecfc726e4155f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 14:32:33 -0700 Subject: [PATCH 037/161] Fix compile errors depending on old versioning system. --- .github/workflows/ccpp.yml | 1 + tf2_bot_detector/Networking/GithubAPI.cpp | 4 ++-- tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp | 2 ++ tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp | 7 +++++++ tf2_bot_detector/UI/MainWindow.cpp | 2 +- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 4a0dba8d..db4c7f8b 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -82,6 +82,7 @@ jobs: echo "matrix.build_arch = ${{ matrix.build_arch }}" echo "matrix.build_type = ${{ matrix.build_type }}" echo "env.TF2BD_ENABLE_ARTIFACT_UPLOAD = ${{ env.TF2BD_ENABLE_ARTIFACT_UPLOAD }}" + echo "env.TF2BD_VERSION = ${{ env.TF2BD_VERSION }}" echo "steps.tf2bd_paths.outputs.workspace = ${{ steps.tf2bd_paths.outputs.workspace }}" echo "steps.tf2bd_paths.outputs.build_dir = ${{ steps.tf2bd_paths.outputs.build_dir }}" diff --git a/tf2_bot_detector/Networking/GithubAPI.cpp b/tf2_bot_detector/Networking/GithubAPI.cpp index 55b09371..1f8eff56 100644 --- a/tf2_bot_detector/Networking/GithubAPI.cpp +++ b/tf2_bot_detector/Networking/GithubAPI.cpp @@ -24,7 +24,7 @@ using namespace tf2_bot_detector::GithubAPI; static std::optional ParseSemVer(const char* version) { Version v{}; - auto parsed = sscanf_s(version, "%i.%i.%i.%i", &v.m_Major, &v.m_Minor, &v.m_Patch, &v.m_Preview); + auto parsed = sscanf_s(version, "%i.%i.%i.%i", &v.m_Major, &v.m_Minor, &v.m_Patch, &v.m_Build); if (parsed < 2) return std::nullopt; @@ -88,7 +88,7 @@ static NewVersionResult GetLatestVersion(const HTTPClient& client) break; } - if (release.m_Version.m_Preview != 0) + if (release.m_IsPrerelease) retVal.m_Preview = release; else retVal.m_Stable = release; diff --git a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp index cf4802c0..cfe52b68 100644 --- a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp +++ b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp @@ -12,8 +12,10 @@ static bool InternalValidateSettings(const T& settings) return false; if (settings.m_AllowInternetUsage.value() && settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Unknown) return false; +#if 0 if (settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Releases && VERSION.m_Preview != 0) return false; +#endif return true; } diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp index c5263b6a..e93b7c4f 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp @@ -529,9 +529,11 @@ bool tf2_bot_detector::Combo(const char* label_id, ProgramUpdateCheckMode& mode) const auto oldMode = mode; +#if 0 constexpr bool allowReleases = VERSION.m_Preview == 0; if (!allowReleases && mode == ProgramUpdateCheckMode::Releases) mode = ProgramUpdateCheckMode::Previews; +#endif switch (mode) { @@ -546,6 +548,7 @@ bool tf2_bot_detector::Combo(const char* label_id, ProgramUpdateCheckMode& mode) if (ImGui::Selectable(FRIENDLY_TEXT_DISABLED)) mode = ProgramUpdateCheckMode::Disabled; +#if 0 ImGui::EnabledSwitch(allowReleases, [&] { ImGui::BeginGroup(); @@ -555,6 +558,10 @@ bool tf2_bot_detector::Combo(const char* label_id, ProgramUpdateCheckMode& mode) ImGui::EndGroup(); }, "Since you are using a preview build, you will always be notified of new previews."); +#else + if (ImGui::Selectable(FRIENDLY_TEXT_STABLE)) + mode = ProgramUpdateCheckMode::Releases; +#endif if (ImGui::Selectable(FRIENDLY_TEXT_PREVIEW)) mode = ProgramUpdateCheckMode::Previews; diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index cabe36f3..db842190 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -758,7 +758,7 @@ void MainWindow::OnDrawMenuBar() ImGui::Separator(); - static const mh::fmtstr<128> VERSION_STRING_LABEL("Version: {}", VERSION_STRING); + static const mh::fmtstr<128> VERSION_STRING_LABEL("Version: {}", VERSION); ImGui::MenuItem(VERSION_STRING_LABEL.c_str(), nullptr, false, false); if (m_Settings.m_AllowInternetUsage.value_or(false)) From c5ea13bc7654ccb488c63ccbe6a01b3cdcf7c72c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 14:49:11 -0700 Subject: [PATCH 038/161] Fix conflicting names for msix bundles --- .github/workflows/ccpp.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index db4c7f8b..083a5792 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -271,10 +271,14 @@ jobs: name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" path: msix/bundle_staging + - name: Config msixbundle name + shell: bash + run: echo "::set-env name=TF2BD_MSIXBUNDLE_NAME::tf2-bot-detector_windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msixbundle" + - name: Create bundle run: | cd msix - makeappx bundle /d bundle_staging /bv 1.1.0.11 /p tf2_bot_detector.msixbundle + makeappx bundle /d bundle_staging /bv 1.1.0.11 /p ${{ env.TF2BD_MSIXBUNDLE_NAME }} - name: Sign bundle uses: PazerOP/code-sign-action@v3 @@ -286,6 +290,6 @@ jobs: - name: Upload bundle uses: actions/upload-artifact@v2 with: - name: tf2_bot_detector.msixbundle - path: "msix/tf2_bot_detector.msixbundle" + name: ${{ env.TF2BD_MSIXBUNDLE_NAME }} + path: "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" From 45103a9d7e7b4d6d8dc320235959c18ebc1638be Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 14:49:24 -0700 Subject: [PATCH 039/161] Fix incorrect version for msixbundle --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 083a5792..20a0b1f9 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -278,7 +278,7 @@ jobs: - name: Create bundle run: | cd msix - makeappx bundle /d bundle_staging /bv 1.1.0.11 /p ${{ env.TF2BD_MSIXBUNDLE_NAME }} + makeappx bundle /d bundle_staging /bv ${{ env.TF2BD_VERSION }} /p ${{ env.TF2BD_MSIXBUNDLE_NAME }} - name: Sign bundle uses: PazerOP/code-sign-action@v3 From 21370842a0cea8de1427c227c7059dcec4a9e188 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 15:07:53 -0700 Subject: [PATCH 040/161] Switch to ninja and make artifact names consistent --- .github/workflows/ccpp.yml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 20a0b1f9..7ffe8d4f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -45,11 +45,9 @@ jobs: - os: windows-latest triplet: x86-windows tf2bd_arch: x86 - build_arch: Win32 - os: windows-latest triplet: x64-windows tf2bd_arch: x64 - build_arch: x64 steps: - name: Retrieve TF2BD_VERSION @@ -79,7 +77,6 @@ jobs: echo "matrix.triplet = ${{ matrix.triplet }}" echo "matrix.discord_integration = ${{ matrix.discord_integration }}" echo "matrix.tf2bd_arch = ${{ matrix.tf2bd_arch }}" - echo "matrix.build_arch = ${{ matrix.build_arch }}" echo "matrix.build_type = ${{ matrix.build_type }}" echo "env.TF2BD_ENABLE_ARTIFACT_UPLOAD = ${{ env.TF2BD_ENABLE_ARTIFACT_UPLOAD }}" echo "env.TF2BD_VERSION = ${{ env.TF2BD_VERSION }}" @@ -101,13 +98,18 @@ jobs: vcpkgTriplet: ${{ matrix.triplet }} appendedCacheKey: ${{ hashFiles(env.VCPKG_RESPONSE_FILE) }} + - name: Configure build tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.tf2bd_arch }} + - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} shell: bash run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -A${{ matrix.build_arch }} \ + cmake -G Ninja \ -DTF2BD_IS_CI_COMPILE=ON \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ @@ -153,7 +155,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: - name: "tf2_bot_detector_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + name: "tf2-bot-detector_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" if-no-files-found: error path: ${{ steps.tf2bd_paths.outputs.workspace }}/staging/ @@ -192,7 +194,7 @@ jobs: echo "matrix.build_type.suffix = ${{ matrix.build_type.suffix }}" - name: Config msix filename - run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" + run: echo "::set-env name=TF2BD_MSIX_FILENAME::tf2-bot-detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" - name: Configure PATH for windows sdk uses: ilammy/msvc-dev-cmd@v1 @@ -203,7 +205,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@v2 with: - name: "tf2_bot_detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}" + name: "tf2-bot-detector_${{ matrix.tf2bd_arch }}-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}" path: msix/package_staging/app - name: Mark as non-portable @@ -263,12 +265,12 @@ jobs: - name: Download msix (x86) uses: actions/download-artifact@v2 with: - name: "tf2_bot_detector_x86-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" + name: "tf2-bot-detector_x86-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" path: msix/bundle_staging - name: Download msix (x64) uses: actions/download-artifact@v2 with: - name: "tf2_bot_detector_x64-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" + name: "tf2-bot-detector_x64-windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msix" path: msix/bundle_staging - name: Config msixbundle name From d2beb9e78ff618efb9e71cd19a5e17c18553b8e6 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 15:29:26 -0700 Subject: [PATCH 041/161] Add suffix to the application ID and displayname as well. --- msix/package_staging/AppxManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 71d49809..c9647306 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -21,8 +21,8 @@ - - + + From 23b8a48f26bc0fe32ffd1b0ba6a9d647cd78699f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 16:37:30 -0700 Subject: [PATCH 042/161] Fixed invalid application id suffix --- .github/workflows/ccpp.yml | 5 +++-- msix/package_staging/AppxManifest.xml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7ffe8d4f..14ac708e 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -168,7 +168,6 @@ jobs: - msix: runs-on: windows-latest needs: build @@ -179,6 +178,7 @@ jobs: build_type: - name: Debug suffix: -debug + appid_suffix: Debug - name: Release steps: @@ -218,7 +218,8 @@ jobs: cat msix/package_staging/AppxManifest.xml \ | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ | sed 's/TF2BD_BUILD_NUMBER_REPLACE_ME/${{ github.run_number }}/g' \ - | sed 's/TF2BD_IDENTITY_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ + | sed 's/TF2BD_PACKAGE_IDENTITY_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ + | sed 's/TF2BD_APPLICATION_IDENTITY_SUFFIX_REPLACE_ME/${{ matrix.build_type.appid_suffix }}/g' \ | sed 's/TF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ | tee msix/package_staging/AppxManifest.xml diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index c9647306..41e20e8b 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -2,7 +2,7 @@ - + TF2 Bot DetectorTF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME pazer @@ -21,7 +21,7 @@ - + From b275bf011af8e5889ef2ae78a752f57fab9122be Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 17:26:26 -0700 Subject: [PATCH 043/161] Added option to File menu to browse to the cfg/logs folder --- submodules/mh_stuff | 2 +- tf2_bot_detector/Config/ConfigHelpers.cpp | 21 ++++++++------------- tf2_bot_detector/Config/ConfigHelpers.h | 6 +++--- tf2_bot_detector/Filesystem.h | 14 ++++++++++++++ tf2_bot_detector/UI/MainWindow.cpp | 9 +++++++++ 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index c71a18b7..206fb329 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit c71a18b7014201364b34273af8f19daa33d4add2 +Subproject commit 206fb329f2ce9aeddbd73e0b6897810e659a7faa diff --git a/tf2_bot_detector/Config/ConfigHelpers.cpp b/tf2_bot_detector/Config/ConfigHelpers.cpp index d4ebb5f2..fec3bc07 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.cpp +++ b/tf2_bot_detector/Config/ConfigHelpers.cpp @@ -7,6 +7,7 @@ #include "Log.h" #include "Version.h" +#include #include #include #include @@ -21,31 +22,25 @@ auto tf2_bot_detector::GetConfigFilePaths(const std::string_view& basename) -> C { ConfigFilePaths retVal; - { - const std::filesystem::path cfg("cfg"); - if (auto found = IFilesystem::Get().ResolvePath(cfg / mh::format("{}.official.json", basename), PathUsage::Read); !found.empty()) - retVal.m_Official = found; - if (auto found = IFilesystem::Get().ResolvePath(cfg / mh::format("{}.json", basename), PathUsage::Read); !found.empty()) - retVal.m_User = found; - } + if (auto path = mh::format("cfg/{}.official.json", basename); IFilesystem::Get().Exists(path)) + retVal.m_Official = path; + if (auto path = mh::format("cfg/{}.json", basename); IFilesystem::Get().Exists(path)) + retVal.m_User = path; if (const auto cfg = IFilesystem::Get().GetMutableDataDir() / std::filesystem::path("cfg"); std::filesystem::is_directory(cfg)) { try { - const std::regex s_PlayerListRegex(std::string(basename) << R"regex(\.(.*\.)?json)regex", std::regex::optimize); + const std::regex filenameRegex(mh::format("{}{}", basename, R"regex(\.(?!official).*\.json)regex"), + std::regex::optimize | std::regex::icase); for (const auto& file : std::filesystem::directory_iterator(cfg, std::filesystem::directory_options::follow_directory_symlink | std::filesystem::directory_options::skip_permission_denied)) { const auto path = file.path(); const auto filename = path.filename().string(); - if (mh::case_insensitive_compare(filename, std::string(basename) << ".json"sv)) - retVal.m_User = cfg / filename; - else if (mh::case_insensitive_compare(filename, std::string(basename) << ".official.json"sv)) - retVal.m_Official = cfg / filename; - else if (std::regex_match(filename.begin(), filename.end(), s_PlayerListRegex)) + if (std::regex_match(filename.begin(), filename.end(), filenameRegex)) retVal.m_Others.push_back(cfg / filename); } } diff --git a/tf2_bot_detector/Config/ConfigHelpers.h b/tf2_bot_detector/Config/ConfigHelpers.h index f4a44688..6ca2b595 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.h +++ b/tf2_bot_detector/Config/ConfigHelpers.h @@ -152,7 +152,7 @@ namespace tf2_bot_detector if (!paths.m_Official.empty()) m_OfficialList = LoadConfigFileAsync(paths.m_Official, !IsOfficial(), *m_Settings); else - m_OfficialList = {}; + m_OfficialList = mh::make_ready_future(); m_ThirdPartyLists = std::async([this, paths] { @@ -180,11 +180,11 @@ namespace tf2_bot_detector const T* defaultMutableList = GetDefaultMutableList(); const T* localList = GetLocalList(); if (localList) - localList->SaveFile(std::filesystem::path("cfg") / mh::format("{}.json", GetBaseFileName())); + localList->SaveFile(mh::format("cfg/{}.json", GetBaseFileName())); if (defaultMutableList && defaultMutableList != localList) { - const auto filename = std::filesystem::path("cfg") / mh::format("{}.official.json", GetBaseFileName()); + const std::filesystem::path filename = mh::format("cfg/{}.official.json", GetBaseFileName()); if (!IsOfficial()) throw std::runtime_error("Attempted to save non-official data to "s << filename); diff --git a/tf2_bot_detector/Filesystem.h b/tf2_bot_detector/Filesystem.h index 923399c2..26bd6cad 100644 --- a/tf2_bot_detector/Filesystem.h +++ b/tf2_bot_detector/Filesystem.h @@ -38,6 +38,20 @@ namespace tf2_bot_detector { return WriteFile(path, data.data(), data.data() + data.size()); } + + std::filesystem::path GetLogsDir() const + { + return GetMutableDataDir() / "logs"; + } + std::filesystem::path GetConfigDir() const + { + return GetMutableDataDir() / "cfg"; + } + + bool Exists(const std::filesystem::path& path) const + { + return !ResolvePath(path, PathUsage::Read).empty(); + } }; } diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index db842190..e951b986 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -689,12 +689,21 @@ void MainWindow::OnDrawMenuBar() if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Open Config Folder")) + Shell::OpenURL(IFilesystem::Get().GetConfigDir().string()); + if (ImGui::MenuItem("Open Logs Folder")) + Shell::OpenURL(IFilesystem::Get().GetLogsDir().string()); + + ImGui::Separator(); + if (!isInSetupFlow) { if (ImGui::MenuItem("Reload Playerlists/Rules")) GetModLogic().ReloadConfigFiles(); if (ImGui::MenuItem("Reload Settings")) m_Settings.LoadFile(); + + ImGui::Separator(); } if (ImGui::MenuItem("Generate Debug Report")) From d441ac6b7fe590637af20839953778ec75770fb8 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 17:30:34 -0700 Subject: [PATCH 044/161] Removed missing include --- tf2_bot_detector/Config/ConfigHelpers.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tf2_bot_detector/Config/ConfigHelpers.cpp b/tf2_bot_detector/Config/ConfigHelpers.cpp index fec3bc07..640209d5 100644 --- a/tf2_bot_detector/Config/ConfigHelpers.cpp +++ b/tf2_bot_detector/Config/ConfigHelpers.cpp @@ -7,7 +7,6 @@ #include "Log.h" #include "Version.h" -#include #include #include #include From 9bd1b3af84f5a4b348791871ca876ffcd90e410a Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 20:06:09 -0700 Subject: [PATCH 045/161] Update tf2-bot-detector.appinstaller --- msix/tf2-bot-detector.appinstaller | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index 40668cec..1ecc8e76 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -6,12 +6,20 @@ + Publisher="CN=Matt Haynie, O=Matt Haynie, STREET=2416 201st Ave SE, L=Sammamish, S=Washington, PostalCode=98075, C=US" Version="1.1.0.524" + Uri="http://home.pazer.us/tf2_bot_detector/tf2-bot-detector_windows_1.1.0.524.msixbundle" /> - - + + From 6efc4e4c498f26a2178cf6e0b241742414e34271 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 20:06:48 -0700 Subject: [PATCH 046/161] Update tf2-bot-detector.appinstaller --- msix/tf2-bot-detector.appinstaller | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index 1ecc8e76..9124d38a 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -4,17 +4,16 @@ Version="1.0.1.0" Uri="https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller"> - - + ProcessorArchitecture="x64" + Uri="http://home.pazer.us/tf2_bot_detector/Microsoft.VCLibs.x64.14.00.Desktop.appx" /> Date: Fri, 21 Aug 2020 20:11:45 -0700 Subject: [PATCH 047/161] Check for updates on every launch --- msix/tf2-bot-detector.appinstaller | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index 9124d38a..3f382523 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -10,18 +10,18 @@ - + From 2125f2fb526d916a1a423362d599f0d7bf2e5938 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 21:10:10 -0700 Subject: [PATCH 048/161] Test trying to get the package path --- tf2_bot_detector/Platform/Platform.h | 1 + tf2_bot_detector/Platform/Windows/Windows.cpp | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 7dabe32a..934299c5 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -16,6 +16,7 @@ namespace tf2_bot_detector std::filesystem::path GetCurrentExeDir(); std::filesystem::path GetAppDataDir(); + std::filesystem::path GetRealAppDataDir(); namespace Processes { diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index 1d585d97..1ed81c84 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -1,8 +1,11 @@ #include "Platform/Platform.h" #include "WindowsHelpers.h" +#include + #define WIN32_LEAN_AND_MEAN 1 #include +#include #include std::filesystem::path tf2_bot_detector::Platform::GetCurrentExeDir() @@ -20,8 +23,28 @@ std::filesystem::path tf2_bot_detector::Platform::GetCurrentExeDir() return std::filesystem::path(path, path + length).remove_filename(); } +std::filesystem::path tf2_bot_detector::Platform::GetRealAppDataDir() +{ + WCHAR path[32768]; + UINT32 pathLength = std::size(path); + const auto errc = GetCurrentPackagePath(&pathLength, path); + + switch (errc) + { + case ERROR_SUCCESS: + return std::filesystem::path(path, path + pathLength); + case APPMODEL_ERROR_NO_PACKAGE: + return "";// GetAppDataDir(); // We're not in a package + case ERROR_INSUFFICIENT_BUFFER: + throw std::runtime_error(mh::format("{}: Buffer too small", __FUNCTION__)); + default: + throw std::runtime_error(mh::format("{}: Unknown error {}", __FUNCTION__, errc)); + } +} + std::filesystem::path tf2_bot_detector::Platform::GetAppDataDir() { + auto test = GetRealAppDataDir(); PWSTR str; CHECK_HR(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &str)); From 319ac388cfabad76f3018954cbb1d9ccde1693d1 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 21 Aug 2020 21:23:19 -0700 Subject: [PATCH 049/161] Update version --- msix/tf2-bot-detector.appinstaller | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller index 3f382523..7364f5f5 100644 --- a/msix/tf2-bot-detector.appinstaller +++ b/msix/tf2-bot-detector.appinstaller @@ -5,8 +5,8 @@ Uri="https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller"> + Publisher="CN=Matt Haynie, O=Matt Haynie, STREET=2416 201st Ave SE, L=Sammamish, S=Washington, PostalCode=98075, C=US" Version="1.1.0.528" + Uri="http://home.pazer.us/tf2_bot_detector/tf2-bot-detector_windows_1.1.0.528.msixbundle" /> Date: Fri, 21 Aug 2020 21:24:04 -0700 Subject: [PATCH 050/161] .appinstaller files are never packaged into an msix. Never trigger a rebuild because of a modification of them. --- .github/workflows/ccpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 14ac708e..e3d10c9d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -6,6 +6,7 @@ on: - 'schemas/**.json' - 'staging/cfg/**.json' - '*.md' + - '*.appinstaller' jobs: setup_version: From cb4362ce8cb51a3f3287b31ed101850d4027943a Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 22 Aug 2020 13:39:11 -0700 Subject: [PATCH 051/161] Attempt to get real appdata path from inside program --- tf2_bot_detector/Filesystem.cpp | 14 +++- tf2_bot_detector/Filesystem.h | 13 +++- tf2_bot_detector/Platform/Platform.h | 1 + tf2_bot_detector/Platform/Windows/Shell.cpp | 8 ++- tf2_bot_detector/Platform/Windows/Windows.cpp | 70 ++++++++++++++----- tf2_bot_detector/UI/MainWindow.cpp | 4 +- 6 files changed, 87 insertions(+), 23 deletions(-) diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp index 62a684ff..6ef92e68 100644 --- a/tf2_bot_detector/Filesystem.cpp +++ b/tf2_bot_detector/Filesystem.cpp @@ -22,16 +22,18 @@ namespace std::string ReadFile(std::filesystem::path path) const override; void WriteFile(std::filesystem::path path, const void* begin, const void* end) const override; - std::filesystem::path GetMutableDataDir() const; + std::filesystem::path GetMutableDataDir() const override; + std::filesystem::path GetRealMutableDataDir() const override; private: static constexpr char NON_PORTABLE_MARKER[] = ".non_portable"; + static constexpr char APPDATA_SUBFOLDER[] = "TF2 Bot Detector"; std::vector m_SearchPaths; bool m_IsPortable; const std::filesystem::path m_ExeDir = Platform::GetCurrentExeDir(); const std::filesystem::path m_WorkingDir = std::filesystem::current_path(); - const std::filesystem::path m_AppDataDir = Platform::GetAppDataDir() / "TF2 Bot Detector"; + const std::filesystem::path m_AppDataDir = Platform::GetAppDataDir() / APPDATA_SUBFOLDER; //std::filesystem::path m_MutableDataDir = ChooseMutableDataPath(); }; } @@ -177,3 +179,11 @@ std::filesystem::path Filesystem::GetMutableDataDir() const else return m_AppDataDir; } + +std::filesystem::path Filesystem::GetRealMutableDataDir() const +{ + if (m_IsPortable) + return m_WorkingDir; + else + return Platform::GetRealAppDataDir() / APPDATA_SUBFOLDER; +} diff --git a/tf2_bot_detector/Filesystem.h b/tf2_bot_detector/Filesystem.h index 26bd6cad..239f4c4e 100644 --- a/tf2_bot_detector/Filesystem.h +++ b/tf2_bot_detector/Filesystem.h @@ -29,6 +29,7 @@ namespace tf2_bot_detector virtual std::filesystem::path ResolvePath(const std::filesystem::path& path, PathUsage usage) const = 0; virtual std::filesystem::path GetMutableDataDir() const = 0; + virtual std::filesystem::path GetRealMutableDataDir() const = 0; //virtual std::fstream OpenFile(const std::filesystem::path& path) = 0; virtual std::string ReadFile(std::filesystem::path path) const = 0; @@ -39,13 +40,21 @@ namespace tf2_bot_detector return WriteFile(path, data.data(), data.data() + data.size()); } + static std::filesystem::path GetLogsDir(const std::filesystem::path& baseDataDir) + { + return baseDataDir / "logs"; + } std::filesystem::path GetLogsDir() const { - return GetMutableDataDir() / "logs"; + return GetLogsDir(GetMutableDataDir()); + } + static std::filesystem::path GetConfigDir(const std::filesystem::path& baseDataDir) + { + return baseDataDir / "cfg"; } std::filesystem::path GetConfigDir() const { - return GetMutableDataDir() / "cfg"; + return GetConfigDir(GetMutableDataDir()); } bool Exists(const std::filesystem::path& path) const diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 934299c5..1990f367 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -32,6 +32,7 @@ namespace tf2_bot_detector std::filesystem::path BrowseForFolderDialog(); void ExploreToAndSelect(std::filesystem::path path); + void ExploreTo(const std::filesystem::path& path); void OpenURL(const char* url); inline void OpenURL(const std::string& url) { return OpenURL(url.c_str()); } } diff --git a/tf2_bot_detector/Platform/Windows/Shell.cpp b/tf2_bot_detector/Platform/Windows/Shell.cpp index d5184485..4847f179 100644 --- a/tf2_bot_detector/Platform/Windows/Shell.cpp +++ b/tf2_bot_detector/Platform/Windows/Shell.cpp @@ -70,9 +70,15 @@ std::filesystem::path tf2_bot_detector::Shell::BrowseForFolderDialog() } } +void tf2_bot_detector::Shell::ExploreTo(const std::filesystem::path& path) +{ + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "{}", path); + ShellExecuteW(nullptr, L"explore", path.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} + void tf2_bot_detector::Shell::OpenURL(const char* url) { - DebugLog("Shell opening "s << std::quoted(url)); + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "{}", std::quoted(url)); ShellExecuteA(NULL, "open", url, nullptr, nullptr, SW_SHOWNORMAL); } diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index 1ed81c84..2f203aac 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -23,33 +23,71 @@ std::filesystem::path tf2_bot_detector::Platform::GetCurrentExeDir() return std::filesystem::path(path, path + length).remove_filename(); } -std::filesystem::path tf2_bot_detector::Platform::GetRealAppDataDir() +namespace tf2_bot_detector { - WCHAR path[32768]; - UINT32 pathLength = std::size(path); - const auto errc = GetCurrentPackagePath(&pathLength, path); + static std::wstring GetCurrentPackageFamilyName() + { + WCHAR name[PACKAGE_FAMILY_NAME_MAX_LENGTH + 1]; + UINT32 nameLength = std::size(name); + const auto errc = ::GetCurrentPackageFamilyName(&nameLength, name); + + switch (errc) + { + case ERROR_SUCCESS: + return std::wstring(name, name + nameLength); + case APPMODEL_ERROR_NO_PACKAGE: + return {}; + case ERROR_INSUFFICIENT_BUFFER: + throw std::runtime_error(mh::format("{}: Buffer too small", __FUNCTION__)); + default: + throw std::runtime_error(mh::format("{}: Unknown error {}", __FUNCTION__, errc)); + } + } - switch (errc) + static std::filesystem::path GetCurrentPackagePath() { - case ERROR_SUCCESS: - return std::filesystem::path(path, path + pathLength); - case APPMODEL_ERROR_NO_PACKAGE: - return "";// GetAppDataDir(); // We're not in a package - case ERROR_INSUFFICIENT_BUFFER: - throw std::runtime_error(mh::format("{}: Buffer too small", __FUNCTION__)); - default: - throw std::runtime_error(mh::format("{}: Unknown error {}", __FUNCTION__, errc)); + WCHAR path[32768]; + UINT32 pathLength = std::size(path); + const auto errc = ::GetCurrentPackagePath(&pathLength, path); + + switch (errc) + { + case ERROR_SUCCESS: + return std::filesystem::path(path, path + pathLength); + case APPMODEL_ERROR_NO_PACKAGE: + return "";// GetAppDataDir(); // We're not in a package + case ERROR_INSUFFICIENT_BUFFER: + throw std::runtime_error(mh::format("{}: Buffer too small", __FUNCTION__)); + default: + throw std::runtime_error(mh::format("{}: Unknown error {}", __FUNCTION__, errc)); + } } } -std::filesystem::path tf2_bot_detector::Platform::GetAppDataDir() +static std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) { - auto test = GetRealAppDataDir(); PWSTR str; - CHECK_HR(SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &str)); + CHECK_HR(SHGetKnownFolderPath(id, 0, nullptr, &str)); std::filesystem::path retVal(str); CoTaskMemFree(str); return retVal; } + +std::filesystem::path tf2_bot_detector::Platform::GetRealAppDataDir() +{ + const auto lad = GetKnownFolderPath(FOLDERID_LocalAppData); + + const auto packageAppDataDir = lad / "Packages" / GetCurrentPackageFamilyName() / "LocalCache" / "Roaming"; + + if (std::filesystem::exists(packageAppDataDir)) + return packageAppDataDir; + else + return GetAppDataDir(); +} + +std::filesystem::path tf2_bot_detector::Platform::GetAppDataDir() +{ + return GetKnownFolderPath(FOLDERID_RoamingAppData); +} diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index e951b986..97235ec3 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -690,9 +690,9 @@ void MainWindow::OnDrawMenuBar() if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("Open Config Folder")) - Shell::OpenURL(IFilesystem::Get().GetConfigDir().string()); + Shell::ExploreTo(IFilesystem::GetConfigDir(IFilesystem::Get().GetRealMutableDataDir())); if (ImGui::MenuItem("Open Logs Folder")) - Shell::OpenURL(IFilesystem::Get().GetLogsDir().string()); + Shell::ExploreTo(IFilesystem::GetLogsDir(IFilesystem::Get().GetRealMutableDataDir())); ImGui::Separator(); From 38adc50ff7e327b124877306985d5b207a4f1ac9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 22 Aug 2020 13:50:10 -0700 Subject: [PATCH 052/161] Show timestamps from ninja in the output. --- .github/workflows/ccpp.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index e3d10c9d..a3cfc94f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -104,6 +104,9 @@ jobs: with: arch: ${{ matrix.tf2bd_arch }} + - name: Config Ninja + run: echo "::set-env name=NINJA_STATUS::[%f/%t/%e] " + - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} shell: bash From 27998e06583f46369ce7a2f5c7c77fa3c7a940ea Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 22 Aug 2020 13:56:58 -0700 Subject: [PATCH 053/161] Fix null terminators in output. --- tf2_bot_detector/Platform/Windows/Windows.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index 2f203aac..97c7710e 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -34,7 +34,7 @@ namespace tf2_bot_detector switch (errc) { case ERROR_SUCCESS: - return std::wstring(name, name + nameLength); + return std::wstring(name, nameLength > 0 ? (nameLength - 1) : 0); case APPMODEL_ERROR_NO_PACKAGE: return {}; case ERROR_INSUFFICIENT_BUFFER: @@ -53,7 +53,7 @@ namespace tf2_bot_detector switch (errc) { case ERROR_SUCCESS: - return std::filesystem::path(path, path + pathLength); + return std::filesystem::path(path, path + (pathLength > 0 ? (pathLength - 1) : 0)); case APPMODEL_ERROR_NO_PACKAGE: return "";// GetAppDataDir(); // We're not in a package case ERROR_INSUFFICIENT_BUFFER: From 5e578a47b46b95bd6cf2630b0d5cc2d7566b9866 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 22 Aug 2020 13:57:34 -0700 Subject: [PATCH 054/161] Unhelpful --- .github/workflows/ccpp.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index a3cfc94f..e3d10c9d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -104,9 +104,6 @@ jobs: with: arch: ${{ matrix.tf2bd_arch }} - - name: Config Ninja - run: echo "::set-env name=NINJA_STATUS::[%f/%t/%e] " - - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} shell: bash From 663abf51a80a201666d25c84c0c8c7dc6c78856d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 22 Aug 2020 20:25:36 -0700 Subject: [PATCH 055/161] Switch to having a template file --- msix/tf2-bot-detector.appinstaller_template | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 msix/tf2-bot-detector.appinstaller_template diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template new file mode 100644 index 00000000..15d4a44f --- /dev/null +++ b/msix/tf2-bot-detector.appinstaller_template @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + From 51c4e9772f2bdd02391c529e3404c158894e8d16 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 22 Aug 2020 21:00:30 -0700 Subject: [PATCH 056/161] Update appinstaller template --- msix/tf2-bot-detector.appinstaller_template | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 15d4a44f..d286d166 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -2,23 +2,23 @@ + Uri="https://tf2bd-util.pazer.us/AppInstaller/%TF2BD_BUILD_TYPE%"> + Uri="https://tf2bd-util.pazer.us/BuildArtifact/Get/%TF2BD_BUNDLE_VERSION%" /> + Uri="https://tf2bd-util.pazer.us/static/Microsoft.VCLibs.x64.14.00.Desktop.appx" /> + Uri="https://tf2bd-util.pazer.us/static/Microsoft.VCLibs.x86.14.00.Desktop.appx" /> From 28d1f507ab69b2e3c560a1ac349fbee2841401fa Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 14:03:41 -0700 Subject: [PATCH 057/161] Try automatically uploading to tf2bd-util --- .github/workflows/ccpp.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index e3d10c9d..91fd9068 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -7,6 +7,8 @@ on: - 'staging/cfg/**.json' - '*.md' - '*.appinstaller' + release: + types: [released, prereleased] jobs: setup_version: @@ -69,6 +71,7 @@ jobs: - name: Debug run: | + echo "github.event = ${{ github.event }}" echo "github.event_name = ${{ github.event_name }}" echo "github.sha = ${{ github.sha }}" echo "github.ref = ${{ github.ref }}" @@ -291,9 +294,28 @@ jobs: certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - - name: Upload bundle + - name: Upload bundle (github actions artifact) uses: actions/upload-artifact@v2 with: name: ${{ env.TF2BD_MSIXBUNDLE_NAME }} path: "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" + - name: Upload bundle (tf2bd-util) + if: matrix.build_type.name == Release + shell: bash + run: | + BASIC_URL="https://tf2bd-util.pazer.us/buildartifacts/upload?key=${{ secrets.TF2BD_UTIL_UPLOAD_KEY_TEST }}&version=${{ env.TF2BD_VERSION }}&buildtype=" + + if [ "${{ github.event_name }}" = "push" ] + then + curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}continuous" + elif [ "${{ github.event_name }}" = "release" ] + then + curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}public" + elif [ "${{ github.event_name }}" = "schedule" ] + then + curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}preview" + elif [ "${{ github.event_name }}" = "schedule" ] + then + curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}nightly" + fi From eee0e81752978386db2739980484bdd135ac7f13 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 14:04:53 -0700 Subject: [PATCH 058/161] Fix some issues --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 91fd9068..9644e500 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -301,7 +301,7 @@ jobs: path: "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" - name: Upload bundle (tf2bd-util) - if: matrix.build_type.name == Release + if: matrix.build_type.name == "Release" shell: bash run: | BASIC_URL="https://tf2bd-util.pazer.us/buildartifacts/upload?key=${{ secrets.TF2BD_UTIL_UPLOAD_KEY_TEST }}&version=${{ env.TF2BD_VERSION }}&buildtype=" @@ -309,10 +309,10 @@ jobs: if [ "${{ github.event_name }}" = "push" ] then curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}continuous" - elif [ "${{ github.event_name }}" = "release" ] + elif [ "${{ github.event_name }}" = "released" ] then curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}public" - elif [ "${{ github.event_name }}" = "schedule" ] + elif [ "${{ github.event_name }}" = "prereleased" ] then curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}preview" elif [ "${{ github.event_name }}" = "schedule" ] From 7734c06c302c6c5c7f2271e455c4206362c19781 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 14:05:54 -0700 Subject: [PATCH 059/161] dreadful --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 9644e500..8f547478 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -301,7 +301,7 @@ jobs: path: "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" - name: Upload bundle (tf2bd-util) - if: matrix.build_type.name == "Release" + if: matrix.build_type.name == 'Release' shell: bash run: | BASIC_URL="https://tf2bd-util.pazer.us/buildartifacts/upload?key=${{ secrets.TF2BD_UTIL_UPLOAD_KEY_TEST }}&version=${{ env.TF2BD_VERSION }}&buildtype=" From a032871c970ea00c20ed60143fef6ec40b32e7cb Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 14:20:54 -0700 Subject: [PATCH 060/161] Fixes --- .github/workflows/ccpp.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8f547478..eb3f58a3 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -304,18 +304,21 @@ jobs: if: matrix.build_type.name == 'Release' shell: bash run: | - BASIC_URL="https://tf2bd-util.pazer.us/buildartifacts/upload?key=${{ secrets.TF2BD_UTIL_UPLOAD_KEY_TEST }}&version=${{ env.TF2BD_VERSION }}&buildtype=" - if [ "${{ github.event_name }}" = "push" ] then - curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}continuous" + BUILDTYPE=continuous elif [ "${{ github.event_name }}" = "released" ] then - curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}public" + BUILDTYPE=public elif [ "${{ github.event_name }}" = "prereleased" ] then - curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}preview" + BUILDTYPE=preview elif [ "${{ github.event_name }}" = "schedule" ] then - curl -T ${{ env.TF2BD_MSIXBUNDLE_NAME }} "${BASIC_URL}nightly" + BUILDTYPE=nightly + fi + + if [ ! -z "$BUILDTYPE" ] + then + curl -T "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" "https://tf2bd-util.pazer.us/buildartifacts/upload?key=${{ secrets.TF2BD_UTIL_UPLOAD_KEY }}&version=${{ env.TF2BD_VERSION }}&buildtype=$BUILDTYPE" fi From 2d4ea085f45d74ccd85ffd1b968cb5951a0691e9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 16:08:07 -0700 Subject: [PATCH 061/161] what a waste of time --- .github/workflows/ccpp.yml | 25 --------------------- msix/tf2-bot-detector.appinstaller_template | 2 +- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index eb3f58a3..94eb7a26 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -7,8 +7,6 @@ on: - 'staging/cfg/**.json' - '*.md' - '*.appinstaller' - release: - types: [released, prereleased] jobs: setup_version: @@ -299,26 +297,3 @@ jobs: with: name: ${{ env.TF2BD_MSIXBUNDLE_NAME }} path: "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" - - - name: Upload bundle (tf2bd-util) - if: matrix.build_type.name == 'Release' - shell: bash - run: | - if [ "${{ github.event_name }}" = "push" ] - then - BUILDTYPE=continuous - elif [ "${{ github.event_name }}" = "released" ] - then - BUILDTYPE=public - elif [ "${{ github.event_name }}" = "prereleased" ] - then - BUILDTYPE=preview - elif [ "${{ github.event_name }}" = "schedule" ] - then - BUILDTYPE=nightly - fi - - if [ ! -z "$BUILDTYPE" ] - then - curl -T "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" "https://tf2bd-util.pazer.us/buildartifacts/upload?key=${{ secrets.TF2BD_UTIL_UPLOAD_KEY }}&version=${{ env.TF2BD_VERSION }}&buildtype=$BUILDTYPE" - fi diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index d286d166..0241ae00 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -6,7 +6,7 @@ + Uri="%TF2BD_BUNDLE_URL%" /> Date: Sun, 23 Aug 2020 21:14:41 -0700 Subject: [PATCH 062/161] Update appinstaller stuff to reflect tf2bd-util changes --- README.md | 2 +- msix/tf2-bot-detector.appinstaller_template | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ae692e50..b06606dd 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@
-->
- Install + Install · Report a Bug · diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 0241ae00..61252a78 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -13,15 +13,15 @@ Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" Version="14.0.27810.0" ProcessorArchitecture="x64" - Uri="https://tf2bd-util.pazer.us/static/Microsoft.VCLibs.x64.14.00.Desktop.appx" /> + Uri="https://tf2bd-util.pazer.us/cache/static/Microsoft.VCLibs.x64.14.00.Desktop.appx" /> + Uri="https://tf2bd-util.pazer.us/cache/static/Microsoft.VCLibs.x86.14.00.Desktop.appx" />
- +
From a2d6483b08fd7dfcaf022f91ece3fb56163a0bf7 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 21:23:15 -0700 Subject: [PATCH 063/161] Allow testing appinstaller locally --- msix/tf2-bot-detector.appinstaller_template | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 61252a78..7740f9df 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -2,7 +2,7 @@ + Uri="%TF2BD_HOST_URL%/AppInstaller/%TF2BD_BUILD_TYPE%"> + Uri="%TF2BD_HOST_URL%/cache/static/Microsoft.VCLibs.x64.14.00.Desktop.appx" /> + Uri="%TF2BD_HOST_URL%/cache/static/Microsoft.VCLibs.x86.14.00.Desktop.appx" />
From a87b08f307ec161674f19b1ff4e446d3db2ff46e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 21:35:12 -0700 Subject: [PATCH 064/161] oops --- msix/tf2-bot-detector.appinstaller_template | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 7740f9df..61252a78 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -2,7 +2,7 @@ + Uri="https://tf2bd-util.pazer.us/AppInstaller/%TF2BD_BUILD_TYPE%"> + Uri="https://tf2bd-util.pazer.us/cache/static/Microsoft.VCLibs.x64.14.00.Desktop.appx" /> + Uri="https://tf2bd-util.pazer.us/cache/static/Microsoft.VCLibs.x86.14.00.Desktop.appx" />
From 47bdc57799def9806c2e4c68449671e6feedce21 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 23 Aug 2020 21:57:17 -0700 Subject: [PATCH 065/161] Use the bundle version for the appinstaller version as well --- msix/tf2-bot-detector.appinstaller_template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 61252a78..d4228efc 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -1,7 +1,7 @@ Date: Mon, 24 Aug 2020 01:31:26 -0700 Subject: [PATCH 066/161] Fix broken schema --- msix/tf2-bot-detector.appinstaller_template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index d4228efc..8c945e8f 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -1,6 +1,6 @@ @@ -22,6 +22,6 @@ - + From bc08341739e9e8e439ca86edbb41d589c8aff36e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 01:36:31 -0700 Subject: [PATCH 067/161] We can have showprompt, we just need the new schema ref --- msix/tf2-bot-detector.appinstaller_template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 8c945e8f..acff3391 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -22,6 +22,6 @@ - + From fe7b5cc2881a3d7b263fd853cfd1abb1901f202d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 10:29:16 -0700 Subject: [PATCH 068/161] is this really what breaks it --- msix/tf2-bot-detector.appinstaller_template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index acff3391..8c945e8f 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -22,6 +22,6 @@ - + From e5d13e6578bb12fb34a1766b4da962ba3df1d90d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 11:02:04 -0700 Subject: [PATCH 069/161] ...now try rolling back the schema as well --- msix/tf2-bot-detector.appinstaller_template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 8c945e8f..2ac70615 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -1,6 +1,6 @@ From 008244c6a765537e5cfb44dabdf942498d44bd5d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 13:24:53 -0700 Subject: [PATCH 070/161] Fix url having learned my lesson --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b06606dd..5be8b8f0 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@
-->
- Install + Install · Report a Bug · From 99744b89d67783e97a636f55dbdd42287f77fb4e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 13:30:19 -0700 Subject: [PATCH 071/161] Re-add ShowPrompt and newer schema --- msix/tf2-bot-detector.appinstaller_template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template index 2ac70615..acff3391 100644 --- a/msix/tf2-bot-detector.appinstaller_template +++ b/msix/tf2-bot-detector.appinstaller_template @@ -1,6 +1,6 @@ @@ -22,6 +22,6 @@ - + From 0234600c127afd3116d39b8ac0c111fa48fea494 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 13:53:14 -0700 Subject: [PATCH 072/161] Force another build From 69c07643f97cedfb87ca2671a79570a35471d4ac Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 13:58:47 -0700 Subject: [PATCH 073/161] Set ourselves as compatible back to windows 10 release version. Does it work? Who knows, its impossible to find a download for old versions of windows 10 --- msix/package_staging/AppxManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 41e20e8b..507b0ad4 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -13,7 +13,7 @@ - + From 3eb4b718aad9debbcd6bce27974035492f10de5a Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 14:12:03 -0700 Subject: [PATCH 074/161] Never mind, 1809 is the oldest supported --- msix/package_staging/AppxManifest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 507b0ad4..ab7ea026 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -13,8 +13,8 @@ - - + + From 15e76970f71fa2a7d30a5b6ee1717a38fc3a6aa9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 20:10:11 -0700 Subject: [PATCH 075/161] Try to show the release channel in the title bar --- CMakeLists.txt | 1 + submodules/mh_stuff | 2 +- tf2_bot_detector/Log.cpp | 14 +++--- tf2_bot_detector/Log.h | 1 + tf2_bot_detector/Platform/Platform.h | 3 ++ tf2_bot_detector/Platform/Windows/Windows.cpp | 43 +++++++++++++++++++ tf2_bot_detector/ReleaseChannel.h | 11 +++++ tf2_bot_detector/UI/MainWindow.cpp | 17 +++++++- 8 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 tf2_bot_detector/ReleaseChannel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 13f56b28..d94e1c92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,7 @@ target_sources(tf2_bot_detector PRIVATE "tf2_bot_detector/ModeratorLogic.cpp" "tf2_bot_detector/ModeratorLogic.h" "tf2_bot_detector/PlayerStatus.h" + "tf2_bot_detector/ReleaseChannel.h" "tf2_bot_detector/SteamID.cpp" "tf2_bot_detector/SteamID.h" "tf2_bot_detector/TextureManager.h" diff --git a/submodules/mh_stuff b/submodules/mh_stuff index 206fb329..c1d865b4 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit 206fb329f2ce9aeddbd73e0b6897810e659a7faa +Subproject commit c1d865b49807628bc50c8bf18022b4651ac9c9bb diff --git a/tf2_bot_detector/Log.cpp b/tf2_bot_detector/Log.cpp index 555f0c4f..b232cee1 100644 --- a/tf2_bot_detector/Log.cpp +++ b/tf2_bot_detector/Log.cpp @@ -217,11 +217,15 @@ void LogManager::ReplaceSecrets(std::string& msg) const } } -void tf2_bot_detector::LogException(const mh::source_location& location, const std::exception& e, - const std::string_view& msg) -{ - LogError(location, msg.empty() ? "{1}: {2}" : "{0}: {1}: {2}", msg, typeid(e).name(), e.what()); -} +#define LOG_EXCEPTION_IMPL(logExFn, logFn) \ + void tf2_bot_detector::logExFn(const mh::source_location& location, const std::exception& e, \ + const std::string_view& msg) \ + { \ + logFn(location, msg.empty() ? "{1}: {2}" : "{0}: {1}: {2}", msg, typeid(e).name(), e.what()); \ + } + +LOG_EXCEPTION_IMPL(LogException, LogError); +LOG_EXCEPTION_IMPL(DebugLogException, DebugLogWarning); void tf2_bot_detector::LogFatalError(const mh::source_location& location, const std::string_view& msg) { diff --git a/tf2_bot_detector/Log.h b/tf2_bot_detector/Log.h index 96bea671..f4b50bc4 100644 --- a/tf2_bot_detector/Log.h +++ b/tf2_bot_detector/Log.h @@ -156,6 +156,7 @@ namespace tf2_bot_detector name(location, e, mh::format(fmtStr, args...)); \ } + LOG_DEFINITION_HELPER(DebugLogException, ); LOG_DEFINITION_HELPER(LogException, ); LOG_DEFINITION_HELPER(LogFatalException, [[noreturn]]); diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 1990f367..f037745b 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -1,9 +1,11 @@ #pragma once +#include "ReleaseChannel.h" #include "SteamID.h" #include #include +#include #include #include @@ -17,6 +19,7 @@ namespace tf2_bot_detector std::filesystem::path GetCurrentExeDir(); std::filesystem::path GetAppDataDir(); std::filesystem::path GetRealAppDataDir(); + std::optional GetPlatformUpdateChannel(); namespace Processes { diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index 97c7710e..d15479be 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -1,12 +1,20 @@ #include "Platform/Platform.h" +#include "Util/TextUtils.h" +#include "Log.h" #include "WindowsHelpers.h" #include +#include #define WIN32_LEAN_AND_MEAN 1 #include #include #include +#include + +#pragma comment(lib, "windowsapp") + +using namespace std::string_view_literals; std::filesystem::path tf2_bot_detector::Platform::GetCurrentExeDir() { @@ -91,3 +99,38 @@ std::filesystem::path tf2_bot_detector::Platform::GetAppDataDir() { return GetKnownFolderPath(FOLDERID_RoamingAppData); } + +auto tf2_bot_detector::Platform::GetPlatformUpdateChannel() -> std::optional +{ + using namespace winrt::Windows::ApplicationModel; + Package package(nullptr); + try + { + package = Package::Current(); + if (!package) + return std::nullopt; + } + catch (const winrt::hresult_error& e) + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), ToMB(e.message())); + return std::nullopt; + } + + const auto installerInfo = package.GetAppInstallerInfo(); + if (!installerInfo) + return std::nullopt; + + const std::wstring url = mh::tolower(installerInfo.Uri().ToString()); + + if (url.ends_with(L"public.appinstaller"sv)) + return ReleaseChannel::Public; + else if (url.ends_with(L"preview.appinstaller"sv)) + return ReleaseChannel::Preview; + else if (url.ends_with(L"nightly.appinstaller"sv)) + return ReleaseChannel::Nightly; + else + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "Unable to associate url {} with a release channel", ToMB(url)); + return std::nullopt; + } +} diff --git a/tf2_bot_detector/ReleaseChannel.h b/tf2_bot_detector/ReleaseChannel.h new file mode 100644 index 00000000..bd0f2b6c --- /dev/null +++ b/tf2_bot_detector/ReleaseChannel.h @@ -0,0 +1,11 @@ +#pragma once + +namespace tf2_bot_detector +{ + enum class ReleaseChannel + { + Public, + Preview, + Nightly, + }; +} diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index 97235ec3..acda3ad6 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -37,8 +37,23 @@ using namespace std::chrono_literals; using namespace std::string_literals; using namespace std::string_view_literals; +static std::string_view GetVersionSuffix() +{ + const auto channel = Platform::GetPlatformUpdateChannel(); + if (!channel.has_value()) + return {}; + + switch (*channel) + { + default: return {}; + case ReleaseChannel::Public: return " (Stable)"sv; + case ReleaseChannel::Preview: return " (Preview)"sv; + case ReleaseChannel::Nightly: return " (Nightly)"sv; + } +} + MainWindow::MainWindow() : - ImGuiDesktop::Window(800, 600, mh::fmtstr<128>("TF2 Bot Detector v{}", VERSION).c_str()), + ImGuiDesktop::Window(800, 600, mh::fmtstr<128>("TF2 Bot Detector v{}{}", VERSION, GetVersionSuffix()).c_str()), m_WorldState(IWorldState::Create(m_Settings)), m_ActionManager(IRCONActionManager::Create(m_Settings, GetWorld())), m_TextureManager(CreateTextureManager()), From ba2942b554282020df43bb1489e4703b0d9cee8f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 21:02:54 -0700 Subject: [PATCH 076/161] Fix for ambiguous type aliases --- submodules/mh_stuff | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index c1d865b4..d0217307 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit c1d865b49807628bc50c8bf18022b4651ac9c9bb +Subproject commit d021730766d0f84551492995158530ec18f89b25 From 5847fae6f1d760f0d6480cafa6500b9ec5959687 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 21:07:09 -0700 Subject: [PATCH 077/161] Readme update --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5be8b8f0..4d7919a9 100644 --- a/README.md +++ b/README.md @@ -70,17 +70,14 @@ These instructions will give a quick overview of getting started with TF2BD. There is also a (very slightly out of date, but still useable) video by CrazyGunman#6724 [here][install-video]. -### Prerequisites - ->A note about 64 bit vs 32 bit: If your computer was made after 2011 it is 64 bit and you should use the 64 bit version. - -This program requires [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019][mscr-link] ([32bit version available here][mscr86-link]) - ### Installation #### Automatic Install -If you are using Windows 10 1709 or newer, just click this link: [Install][msix-install-link] +If you are using Windows 10 1809 or newer, just click this link: [Install][msix-install-link] + +#### Powershell Install +`Add-AppxPackage -AppInstallerFile https://tf2bd-util.pazer.us/AppInstaller/Nightly.appinstaller` #### Manual/Portable Install @@ -190,7 +187,7 @@ Huge thanks to the people sponsoring this project via [GitHub Sponsors][github-s [discord-link]: https://discord.gg/W8ZSh3Z [mscr-link]: https://aka.ms/vs/16/release/vc_redist.x64.exe [mscr86-link]: https://aka.ms/vs/16/release/vc_redist.x86.exe -[msix-install-link]: https://tf2bd-github-api.azurewebsites.net/api/AppInstallerRedirect?source=https://raw.githubusercontent.com/PazerOP/tf2_bot_detector/msix_package/msix/tf2-bot-detector.appinstaller +[msix-install-link]: https://tf2bd-util.pazer.us/Redirect/AppInstaller?source=https://tf2bd-util.pazer.us/AppInstaller/Nightly.appinstaller [zip-image]: https://i.imgur.com/ZeCuUul.png [github-sponsors-pazerop]: https://github.com/sponsors/PazerOP [wiki-customization-link]: https://github.com/PazerOP/tf2_bot_detector/wiki/Customization#third-party-player-lists From 1856323b097d8706e751a6ba09b4b5f542728c15 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 24 Aug 2020 21:31:19 -0700 Subject: [PATCH 078/161] Fix compile error --- tf2_bot_detector/Platform/Windows/Windows.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index d15479be..ff1e9553 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #pragma comment(lib, "windowsapp") From c1dd29fd142d3bef80fb3abc18c4164db46429c2 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 25 Aug 2020 15:28:57 -0700 Subject: [PATCH 079/161] Switch to providing the full TF2BD_VERSION in the appxmanifest template --- .github/workflows/ccpp.yml | 2 +- msix/package_staging/AppxManifest.xml | 2 +- msix/tf2-bot-detector.appinstaller | 27 --------------------------- 3 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 msix/tf2-bot-detector.appinstaller diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 94eb7a26..8ea1496a 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -219,7 +219,7 @@ jobs: run: | cat msix/package_staging/AppxManifest.xml \ | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ - | sed 's/TF2BD_BUILD_NUMBER_REPLACE_ME/${{ github.run_number }}/g' \ + | sed 's/TF2BD_VERSION_REPLACE_ME/${{ env.TF2BD_VERSION }}/g' \ | sed 's/TF2BD_PACKAGE_IDENTITY_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ | sed 's/TF2BD_APPLICATION_IDENTITY_SUFFIX_REPLACE_ME/${{ matrix.build_type.appid_suffix }}/g' \ | sed 's/TF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME/${{ matrix.build_type.suffix }}/g' \ diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index ab7ea026..78af275d 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -2,7 +2,7 @@ - + TF2 Bot DetectorTF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME pazer diff --git a/msix/tf2-bot-detector.appinstaller b/msix/tf2-bot-detector.appinstaller deleted file mode 100644 index 7364f5f5..00000000 --- a/msix/tf2-bot-detector.appinstaller +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - From 5f6edabdd2f26cf093f0364541d39cc210ec7d20 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 25 Aug 2020 16:33:27 -0700 Subject: [PATCH 080/161] Don't trigger builds on appinstaller_template changes. --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8ea1496a..7e9b67df 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -6,7 +6,7 @@ on: - 'schemas/**.json' - 'staging/cfg/**.json' - '*.md' - - '*.appinstaller' + - '*.appinstaller_template' jobs: setup_version: From 2b3fae01f580491ae5f414f7648431920adc8ad0 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 25 Aug 2020 21:14:05 -0700 Subject: [PATCH 081/161] Try adding a protocol handler for tf2bd --- msix/package_staging/AppxManifest.xml | 10 +++++++++- msix/package_staging/{logo_256.png => logo.png} | Bin 2 files changed, 9 insertions(+), 1 deletion(-) rename msix/package_staging/{logo_256.png => logo.png} (100%) diff --git a/msix/package_staging/AppxManifest.xml b/msix/package_staging/AppxManifest.xml index 78af275d..49197039 100644 --- a/msix/package_staging/AppxManifest.xml +++ b/msix/package_staging/AppxManifest.xml @@ -7,7 +7,7 @@ TF2 Bot DetectorTF2BD_DISPLAYNAME_SUFFIX_REPLACE_ME pazer Automatically detects and votekicks cheaters/bots in TF2 casual. - logo_256.png + logo.png @@ -23,6 +23,14 @@ + + + + logo.png + TF2 Bot Detector URI Handler + + + diff --git a/msix/package_staging/logo_256.png b/msix/package_staging/logo.png similarity index 100% rename from msix/package_staging/logo_256.png rename to msix/package_staging/logo.png From a544de67324c86c4d759b1fffac5150df0ed87fd Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Thu, 27 Aug 2020 00:20:27 -0700 Subject: [PATCH 082/161] Added new update checking mechanism: * Part of setup flow, no more "update available" popping up right as tf2 automatically opens * (Future) support for self-updating (for both portable and platform-specific installed version), no more need to manually download and install the new update --- CMakeLists.txt | 5 + submodules/mh_stuff | 2 +- tf2_bot_detector/CachedVariable.h | 39 --- tf2_bot_detector/Config/Settings.cpp | 43 +-- tf2_bot_detector/Config/Settings.h | 35 +- tf2_bot_detector/Log.h | 9 +- tf2_bot_detector/Networking/GithubAPI.cpp | 12 +- tf2_bot_detector/Platform/Platform.h | 15 +- .../Platform/Windows/PlatformInstall.cpp | 105 ++++++ tf2_bot_detector/Platform/Windows/Windows.cpp | 39 --- tf2_bot_detector/ReleaseChannel.h | 11 + .../SetupFlow/BasicSettingsPage.cpp | 4 +- .../SetupFlow/BasicSettingsPage.h | 2 +- .../SetupFlow/ChatWrappersGeneratorPage.cpp | 5 +- .../SetupFlow/ChatWrappersGeneratorPage.h | 2 +- .../SetupFlow/ChatWrappersVerifyPage.cpp | 7 +- .../SetupFlow/ChatWrappersVerifyPage.h | 2 +- tf2_bot_detector/SetupFlow/ISetupFlowPage.h | 14 +- .../SetupFlow/NetworkSettingsPage.cpp | 21 +- .../SetupFlow/NetworkSettingsPage.h | 6 +- tf2_bot_detector/SetupFlow/SetupFlow.cpp | 12 +- .../SetupFlow/TF2CommandLinePage.cpp | 8 +- .../SetupFlow/TF2CommandLinePage.h | 2 +- .../SetupFlow/UpdateCheckPage.cpp | 159 +++++++++ tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp | 35 +- tf2_bot_detector/UI/ImGui_TF2BotDetector.h | 4 +- tf2_bot_detector/UI/MainWindow.cpp | 188 +---------- tf2_bot_detector/UI/MainWindow.h | 15 +- tf2_bot_detector/UpdateManager.cpp | 317 ++++++++++++++++++ tf2_bot_detector/UpdateManager.h | 83 +++++ tf2_bot_detector/Util/JSONUtils.h | 21 ++ tf2_bot_detector/Version.base.h | 38 ++- tf2_bot_detector/Version.cpp | 13 + 33 files changed, 880 insertions(+), 393 deletions(-) delete mode 100644 tf2_bot_detector/CachedVariable.h create mode 100644 tf2_bot_detector/Platform/Windows/PlatformInstall.cpp create mode 100644 tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp create mode 100644 tf2_bot_detector/UpdateManager.cpp create mode 100644 tf2_bot_detector/UpdateManager.h create mode 100644 tf2_bot_detector/Version.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d94e1c92..72112f7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,7 @@ target_sources(tf2_bot_detector PRIVATE "tf2_bot_detector/SetupFlow/SetupFlow.h" "tf2_bot_detector/SetupFlow/TF2CommandLinePage.h" "tf2_bot_detector/SetupFlow/TF2CommandLinePage.cpp" + "tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp" "tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp" "tf2_bot_detector/UI/ImGui_TF2BotDetector.h" "tf2_bot_detector/UI/MainWindow.cpp" @@ -162,7 +163,10 @@ target_sources(tf2_bot_detector PRIVATE "tf2_bot_detector/TextureManager.h" "tf2_bot_detector/TextureManager.cpp" "tf2_bot_detector/TFConstants.h" + "tf2_bot_detector/UpdateManager.h" + "tf2_bot_detector/UpdateManager.cpp" "tf2_bot_detector/Version.h" + "tf2_bot_detector/Version.cpp" "tf2_bot_detector/WorldEventListener.cpp" "tf2_bot_detector/WorldEventListener.h" "tf2_bot_detector/WorldState.cpp" @@ -187,6 +191,7 @@ if(WIN32) "tf2_bot_detector/Platform/Windows/WindowsHelpers.h" "tf2_bot_detector/Resources.rc" "tf2_bot_detector/Platform/Windows/Windows.cpp" + "tf2_bot_detector/Platform/Windows/PlatformInstall.cpp" ) endif() diff --git a/submodules/mh_stuff b/submodules/mh_stuff index d0217307..8e2af949 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit d021730766d0f84551492995158530ec18f89b25 +Subproject commit 8e2af9494a3967e7305ec25d127013dccb85679d diff --git a/tf2_bot_detector/CachedVariable.h b/tf2_bot_detector/CachedVariable.h deleted file mode 100644 index cae5c198..00000000 --- a/tf2_bot_detector/CachedVariable.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include "Clock.h" - -#include - -namespace tf2_bot_detector -{ - template - class CachedVariable final - { - public: - CachedVariable(duration_t updateInterval, TUpdateFunc updateFunc) : - m_LastUpdated(clock_t::now()), - m_UpdateInterval(updateInterval), - m_UpdateFunc(std::move(updateFunc)), - m_Value(std::invoke(m_UpdateFunc)) - { - } - - const T& GetAndUpdate() - { - const auto now = clock_t::now(); - if ((now - m_LastUpdated) >= m_UpdateInterval) - m_Value = std::invoke(m_UpdateFunc); - - return m_Value; - } - - private: - time_point_t m_LastUpdated{}; - duration_t m_UpdateInterval{}; - [[no_unique_address]] TUpdateFunc m_UpdateFunc{}; - T m_Value{}; - }; - - template - CachedVariable(duration_t d, TUpdateFunc f) -> CachedVariable, TUpdateFunc>; -} diff --git a/tf2_bot_detector/Config/Settings.cpp b/tf2_bot_detector/Config/Settings.cpp index 50b9bb30..02920dc5 100644 --- a/tf2_bot_detector/Config/Settings.cpp +++ b/tf2_bot_detector/Config/Settings.cpp @@ -1,4 +1,5 @@ #include "Settings.h" +#include "Networking/NetworkHelpers.h" #include "Util/JSONUtils.h" #include "Util/PathUtils.h" #include "Filesystem.h" @@ -6,7 +7,7 @@ #include "Log.h" #include "PlayerListJSON.h" #include "Platform/Platform.h" -#include "Networking/NetworkHelpers.h" +#include "ReleaseChannel.h" #include #include @@ -141,37 +142,39 @@ void GeneralSettings::SetSteamAPIKey(std::string key) m_SteamAPIKey = std::move(key); } -void tf2_bot_detector::to_json(nlohmann::json& j, const ProgramUpdateCheckMode& d) +void tf2_bot_detector::to_json(nlohmann::json& j, const ReleaseChannel& d) { + auto value = mh::enum_type::find_value_name(d); + if (value.empty()) + throw std::invalid_argument(mh::format("Unknown ReleaseChannel {}", d)); + + j = mh::tolower(value); + switch (d) { - case ProgramUpdateCheckMode::Unknown: j = nullptr; return; - case ProgramUpdateCheckMode::Releases: j = "releases"; return; - case ProgramUpdateCheckMode::Previews: j = "previews"; return; - case ProgramUpdateCheckMode::Disabled: j = "disabled"; return; + case ReleaseChannel::None: j = "disabled"; return; + case ReleaseChannel::Public: j = "releases"; return; + case ReleaseChannel::Preview: j = "previews"; return; + case ReleaseChannel::Nightly: j = "nightly"; return; default: - throw std::invalid_argument("Unknown ProgramUpdateCheckMode "s << +std::underlying_type_t(d)); + throw std::invalid_argument("Unknown ReleaseChannel "s << +std::underlying_type_t(d)); } } -void tf2_bot_detector::from_json(const nlohmann::json& j, ProgramUpdateCheckMode& d) +void tf2_bot_detector::from_json(const nlohmann::json& j, ReleaseChannel& d) { - if (j.is_null()) - { - d = ProgramUpdateCheckMode::Unknown; - return; - } - auto value = j.get(); if (value == "releases"sv) - d = ProgramUpdateCheckMode::Releases; + d = ReleaseChannel::Public; else if (value == "previews"sv) - d = ProgramUpdateCheckMode::Previews; + d = ReleaseChannel::Preview; + else if (value == "nightly"sv) + d = ReleaseChannel::Nightly; else if (value == "disabled"sv) - d = ProgramUpdateCheckMode::Disabled; + d = ReleaseChannel::None; else - throw std::invalid_argument("Unknown ProgramUpdateCheckMode "s << std::quoted(value)); + throw std::invalid_argument("Unknown ReleaseChannel "s << std::quoted(value)); } Settings::Settings() @@ -224,7 +227,7 @@ void Settings::LoadFile() try_get_to_defaulted(*found, m_SleepWhenUnfocused, "sleep_when_unfocused"); try_get_to_defaulted(*found, m_AutoTempMute, "auto_temp_mute", DEFAULTS.m_AutoTempMute); try_get_to_defaulted(*found, m_AllowInternetUsage, "allow_internet_usage"); - try_get_to_defaulted(*found, m_ProgramUpdateCheckMode, "program_update_check_mode", DEFAULTS.m_ProgramUpdateCheckMode); + try_get_to_defaulted(*found, m_ReleaseChannel, "program_update_check_mode"); try_get_to_defaulted(*found, m_AutoLaunchTF2, "auto_launch_tf2", DEFAULTS.m_AutoLaunchTF2); try_get_to_defaulted(*found, m_AutoChatWarnings, "auto_chat_warnings", DEFAULTS.m_AutoChatWarnings); try_get_to_defaulted(*found, m_AutoChatWarningsConnecting, "auto_chat_warnings_connecting", DEFAULTS.m_AutoChatWarningsConnecting); @@ -268,7 +271,7 @@ bool Settings::SaveFile() const try { { "sleep_when_unfocused", m_SleepWhenUnfocused }, { "auto_temp_mute", m_AutoTempMute }, - { "program_update_check_mode", m_ProgramUpdateCheckMode }, + { "program_update_check_mode", m_ReleaseChannel }, { "steam_api_key", GetSteamAPIKey() }, { "auto_launch_tf2", m_AutoLaunchTF2 }, { "auto_chat_warnings", m_AutoChatWarnings }, diff --git a/tf2_bot_detector/Config/Settings.h b/tf2_bot_detector/Config/Settings.h index ccd2f299..4f6533f3 100644 --- a/tf2_bot_detector/Config/Settings.h +++ b/tf2_bot_detector/Config/Settings.h @@ -18,17 +18,10 @@ namespace srcon namespace tf2_bot_detector { - enum class ProgramUpdateCheckMode - { - Unknown = -1, - - Releases, - Previews, - Disabled, - }; + enum class ReleaseChannel; - void to_json(nlohmann::json& j, const ProgramUpdateCheckMode& d); - void from_json(const nlohmann::json& j, ProgramUpdateCheckMode& d); + void to_json(nlohmann::json& j, const ReleaseChannel& d); + void from_json(const nlohmann::json& j, ReleaseChannel& d); struct AutoDetectedSettings { @@ -64,7 +57,7 @@ namespace tf2_bot_detector bool m_LazyLoadAPIData = true; - ProgramUpdateCheckMode m_ProgramUpdateCheckMode = ProgramUpdateCheckMode::Unknown; + std::optional m_ReleaseChannel; constexpr auto GetAutoVotekickDelay() const { return std::chrono::duration(m_AutoVotekickDelay); } @@ -140,23 +133,3 @@ namespace tf2_bot_detector mutable std::shared_ptr m_HTTPClient; }; } - -template -std::basic_ostream& operator<<(std::basic_ostream& os, tf2_bot_detector::ProgramUpdateCheckMode val) -{ - using ProgramUpdateCheckMode = tf2_bot_detector::ProgramUpdateCheckMode; - -#undef OS_CASE -#define OS_CASE(v) case v : return os << #v - switch (val) - { - OS_CASE(ProgramUpdateCheckMode::Disabled); - OS_CASE(ProgramUpdateCheckMode::Previews); - OS_CASE(ProgramUpdateCheckMode::Releases); - OS_CASE(ProgramUpdateCheckMode::Unknown); - - default: - return os << "ProgramUpdateCheckMode(" << +std::underlying_type_t(val) << ')'; - } -#undef OS_CASE -} diff --git a/tf2_bot_detector/Log.h b/tf2_bot_detector/Log.h index f4b50bc4..31ed62b6 100644 --- a/tf2_bot_detector/Log.h +++ b/tf2_bot_detector/Log.h @@ -118,11 +118,10 @@ namespace tf2_bot_detector template \ NOINLINE inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ { \ - std::string msg = mh::format("{}@{}:{}()", location.file_name(), location.line(), location.function_name()); \ - if (!fmtStr.empty()) \ - msg += ": " + mh::format(fmtStr, args...); \ - \ - name(color, std::move(msg)); \ + if (fmtStr.empty()) \ + name(color, "{}: {}", location, mh::format(fmtStr, args...)); \ + else \ + name(color, "{}", location); \ } \ template \ inline void name(const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ diff --git a/tf2_bot_detector/Networking/GithubAPI.cpp b/tf2_bot_detector/Networking/GithubAPI.cpp index 1f8eff56..b5cb52b4 100644 --- a/tf2_bot_detector/Networking/GithubAPI.cpp +++ b/tf2_bot_detector/Networking/GithubAPI.cpp @@ -21,16 +21,6 @@ using namespace std::string_literals; using namespace tf2_bot_detector; using namespace tf2_bot_detector::GithubAPI; -static std::optional ParseSemVer(const char* version) -{ - Version v{}; - auto parsed = sscanf_s(version, "%i.%i.%i.%i", &v.m_Major, &v.m_Minor, &v.m_Patch, &v.m_Build); - if (parsed < 2) - return std::nullopt; - - return v; -} - namespace { struct InternalRelease : NewVersionResult::Release @@ -59,7 +49,7 @@ static cppcoro::generator GetAllReleases(const HTTPClient& clie retVal.m_URL = releases.at("html_url"); std::string versionTag = releases.at("tag_name"); - if (auto version = ParseSemVer(versionTag.c_str())) + if (auto version = Version::Parse(versionTag.c_str())) retVal.m_Version = *version; else { diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index f037745b..77ac379b 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -1,16 +1,18 @@ #pragma once -#include "ReleaseChannel.h" #include "SteamID.h" +#include "Version.h" #include #include #include #include -#include namespace tf2_bot_detector { + class HTTPClient; + enum class ReleaseChannel; + inline namespace Platform { std::filesystem::path GetCurrentSteamDir(); @@ -19,7 +21,14 @@ namespace tf2_bot_detector std::filesystem::path GetCurrentExeDir(); std::filesystem::path GetAppDataDir(); std::filesystem::path GetRealAppDataDir(); - std::optional GetPlatformUpdateChannel(); + + /// + /// Is there an update available via a platform-specific update mechanism? + /// msix(bundle) on Windows, APT on linux + /// + std::future> CheckForPlatformUpdate(ReleaseChannel rc, const HTTPClient& client); + void BeginPlatformUpdate(ReleaseChannel rc, const HTTPClient& client); + bool IsInstalled(); // As opposed to portable namespace Processes { diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp new file mode 100644 index 00000000..0d00acf5 --- /dev/null +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -0,0 +1,105 @@ +#include "Platform/Platform.h" +#include "Networking/HTTPClient.h" +#include "Networking/HTTPHelpers.h" +#include "Util/TextUtils.h" +#include "Log.h" +#include "Version.h" + +#include + +#include +#include +#include +#include + +#pragma comment(lib, "windowsapp") + +using namespace tf2_bot_detector; + +static winrt::Windows::ApplicationModel::Package GetAppPackage() +{ + static auto s_Package = std::invoke([]() -> winrt::Windows::ApplicationModel::Package + { + try + { + using namespace winrt::Windows::ApplicationModel; + auto package = Package::Current(); + if (!package) + { + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "We are not an installed package"); + return nullptr; + } + + return package; + } + catch (const winrt::hresult_error& e) + { + //DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Unable to confirm we are an installed package: {}", ToMB(e.message())); + return nullptr; + } + }); + + return s_Package; +} + +bool tf2_bot_detector::Platform::IsInstalled() +{ + return !!GetAppPackage(); +} + +std::future> tf2_bot_detector::Platform::CheckForPlatformUpdate( + ReleaseChannel rc, const HTTPClient& client) +{ + if (!IsInstalled()) + { + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Not installed"); + return mh::make_ready_future(std::optional{}); + } + + auto clientPtr = client.shared_from_this(); + + return std::async([rc, clientPtr]() -> std::optional + { + auto result = clientPtr->GetString(mh::format("https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.txt?type={:v}", rc)); + auto version = Version::Parse(result.c_str()); + if (version > VERSION) + return version; + + return std::nullopt; + }); +} + +void tf2_bot_detector::Platform::BeginPlatformUpdate(ReleaseChannel rc, const HTTPClient& client) +{ + using namespace winrt::Windows::ApplicationModel; + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Foundation::Collections; + using namespace winrt::Windows::Management::Deployment; + + PackageManager mgr; + auto appInstallerPackages = mgr.FindPackages(L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + bool appInstallerFound = false; + for (const Package& pkg : appInstallerPackages) + { + appInstallerFound = true; + break; + } + + if (appInstallerFound) + { + Shell::OpenURL(mh::format( + "ms-appinstaller:?source=https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", rc)); + } + else + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "App installer not found, attempting to install via API..."); + const Uri uri = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", rc); + + IVector deps{ winrt::single_threaded_vector() }; + deps.Append(Uri(L"https://tf2bd-util.pazer.us/AppInstaller/vcredist.x64.msix")); + + auto task = mgr.UpdatePackageAsync(uri, deps, DeploymentOptions::ForceApplicationShutdown); + auto result = task.get(); + throw "Not implemented"; + } +} diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index ff1e9553..77d65c0e 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -10,10 +10,6 @@ #include #include #include -#include -#include - -#pragma comment(lib, "windowsapp") using namespace std::string_view_literals; @@ -100,38 +96,3 @@ std::filesystem::path tf2_bot_detector::Platform::GetAppDataDir() { return GetKnownFolderPath(FOLDERID_RoamingAppData); } - -auto tf2_bot_detector::Platform::GetPlatformUpdateChannel() -> std::optional -{ - using namespace winrt::Windows::ApplicationModel; - Package package(nullptr); - try - { - package = Package::Current(); - if (!package) - return std::nullopt; - } - catch (const winrt::hresult_error& e) - { - DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), ToMB(e.message())); - return std::nullopt; - } - - const auto installerInfo = package.GetAppInstallerInfo(); - if (!installerInfo) - return std::nullopt; - - const std::wstring url = mh::tolower(installerInfo.Uri().ToString()); - - if (url.ends_with(L"public.appinstaller"sv)) - return ReleaseChannel::Public; - else if (url.ends_with(L"preview.appinstaller"sv)) - return ReleaseChannel::Preview; - else if (url.ends_with(L"nightly.appinstaller"sv)) - return ReleaseChannel::Nightly; - else - { - DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "Unable to associate url {} with a release channel", ToMB(url)); - return std::nullopt; - } -} diff --git a/tf2_bot_detector/ReleaseChannel.h b/tf2_bot_detector/ReleaseChannel.h index bd0f2b6c..ff82eb8b 100644 --- a/tf2_bot_detector/ReleaseChannel.h +++ b/tf2_bot_detector/ReleaseChannel.h @@ -1,11 +1,22 @@ #pragma once +#include + namespace tf2_bot_detector { enum class ReleaseChannel { + None = -1, // Don't auto update + Public, Preview, Nightly, }; } + +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::ReleaseChannel) + MH_ENUM_REFLECT_VALUE(None) + MH_ENUM_REFLECT_VALUE(Public) + MH_ENUM_REFLECT_VALUE(Preview) + MH_ENUM_REFLECT_VALUE(Nightly) +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector/SetupFlow/BasicSettingsPage.cpp b/tf2_bot_detector/SetupFlow/BasicSettingsPage.cpp index a28e716c..f7d11386 100644 --- a/tf2_bot_detector/SetupFlow/BasicSettingsPage.cpp +++ b/tf2_bot_detector/SetupFlow/BasicSettingsPage.cpp @@ -20,9 +20,9 @@ static bool InternalValidateSettings(const T& settings) return true; } -bool BasicSettingsPage::ValidateSettings(const Settings& settings) const +auto BasicSettingsPage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult { - return InternalValidateSettings(settings); + return InternalValidateSettings(settings) ? ValidateSettingsResult::Success : ValidateSettingsResult::TriggerOpen; } auto BasicSettingsPage::OnDraw(const DrawState& ds) -> OnDrawResult diff --git a/tf2_bot_detector/SetupFlow/BasicSettingsPage.h b/tf2_bot_detector/SetupFlow/BasicSettingsPage.h index e4bd404b..81488660 100644 --- a/tf2_bot_detector/SetupFlow/BasicSettingsPage.h +++ b/tf2_bot_detector/SetupFlow/BasicSettingsPage.h @@ -8,7 +8,7 @@ namespace tf2_bot_detector class BasicSettingsPage final : public ISetupFlowPage { public: - bool ValidateSettings(const Settings& settings) const override; + ValidateSettingsResult ValidateSettings(const Settings& settings) const override; OnDrawResult OnDraw(const DrawState& ds) override; void Init(const Settings& settings) override; diff --git a/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.cpp b/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.cpp index b732b143..b0a4b74f 100644 --- a/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.cpp +++ b/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.cpp @@ -15,9 +15,10 @@ using namespace std::string_literals; using namespace tf2_bot_detector; -bool ChatWrappersGeneratorPage::ValidateSettings(const Settings& settings) const +auto ChatWrappersGeneratorPage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult { - return settings.m_Unsaved.m_ChatMsgWrappers.has_value(); + return settings.m_Unsaved.m_ChatMsgWrappers.has_value() + ? ValidateSettingsResult::Success : ValidateSettingsResult::TriggerOpen; } auto ChatWrappersGeneratorPage::OnDraw(const DrawState& ds) -> OnDrawResult diff --git a/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.h b/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.h index b79ea084..790209fa 100644 --- a/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.h +++ b/tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.h @@ -11,7 +11,7 @@ namespace tf2_bot_detector class ChatWrappersGeneratorPage final : public ISetupFlowPage { public: - bool ValidateSettings(const Settings& settings) const override; + ValidateSettingsResult ValidateSettings(const Settings& settings) const override; OnDrawResult OnDraw(const DrawState& ds) override; void Init(const Settings& settings) override; diff --git a/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.cpp b/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.cpp index ebdf80e6..235f0634 100644 --- a/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.cpp +++ b/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.cpp @@ -12,9 +12,12 @@ using namespace std::string_literals; using namespace tf2_bot_detector; -bool ChatWrappersVerifyPage::ValidateSettings(const Settings& settings) const +auto ChatWrappersVerifyPage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult { - return m_Validation.valid(); + if (!m_Validation.valid()) + return ValidateSettingsResult::TriggerOpen; + + return ValidateSettingsResult::Success; } auto ChatWrappersVerifyPage::OnDraw(const DrawState& ds) -> OnDrawResult diff --git a/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.h b/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.h index ef9e8f97..0ce35d0f 100644 --- a/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.h +++ b/tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.h @@ -11,7 +11,7 @@ namespace tf2_bot_detector class ChatWrappersVerifyPage final : public ISetupFlowPage { public: - bool ValidateSettings(const Settings& settings) const override; + ValidateSettingsResult ValidateSettings(const Settings& settings) const override; OnDrawResult OnDraw(const DrawState& ds) override; void Init(const Settings& settings) override; diff --git a/tf2_bot_detector/SetupFlow/ISetupFlowPage.h b/tf2_bot_detector/SetupFlow/ISetupFlowPage.h index 5b959b72..ab61f91a 100644 --- a/tf2_bot_detector/SetupFlow/ISetupFlowPage.h +++ b/tf2_bot_detector/SetupFlow/ISetupFlowPage.h @@ -3,6 +3,7 @@ namespace tf2_bot_detector { class IActionManager; + class IUpdateManager; class Settings; class ISetupFlowPage @@ -10,7 +11,17 @@ namespace tf2_bot_detector public: virtual ~ISetupFlowPage() = default; - [[nodiscard]] virtual bool ValidateSettings(const Settings& settings) const = 0; + enum class ValidateSettingsResult + { + // Everything is A-OK. No reason to open this page for user interaction. + Success, + + // Either something's wrong, or we need to run some blocking logic. + // Open this page. + TriggerOpen, + }; + + [[nodiscard]] virtual ValidateSettingsResult ValidateSettings(const Settings& settings) const = 0; enum class OnDrawResult { @@ -24,6 +35,7 @@ namespace tf2_bot_detector struct DrawState { IActionManager* m_ActionManager = nullptr; + IUpdateManager* m_UpdateManager = nullptr; Settings* m_Settings = nullptr; }; diff --git a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp index cfe52b68..c91fd68a 100644 --- a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp +++ b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp @@ -1,6 +1,7 @@ #include "NetworkSettingsPage.h" #include "Config/Settings.h" #include "UI/ImGui_TF2BotDetector.h" +#include "ReleaseChannel.h" #include "Version.h" using namespace tf2_bot_detector; @@ -10,7 +11,7 @@ static bool InternalValidateSettings(const T& settings) { if (!settings.m_AllowInternetUsage) return false; - if (settings.m_AllowInternetUsage.value() && settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Unknown) + if (!settings.m_ReleaseChannel.has_value()) return false; #if 0 if (settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Releases && VERSION.m_Preview != 0) @@ -20,9 +21,9 @@ static bool InternalValidateSettings(const T& settings) return true; } -bool NetworkSettingsPage::ValidateSettings(const Settings& settings) const +auto NetworkSettingsPage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult { - return InternalValidateSettings(settings); + return InternalValidateSettings(settings) ? ValidateSettingsResult::Success : ValidateSettingsResult::TriggerOpen; } auto NetworkSettingsPage::OnDraw(const DrawState& ds) -> OnDrawResult @@ -47,11 +48,11 @@ auto NetworkSettingsPage::OnDraw(const DrawState& ds) -> OnDrawResult ImGui::TextFmt("This tool can also check for updated functionality and bugfixes on startup."); ImGui::Indent(); { - auto mode = enabled ? m_Settings.m_ProgramUpdateCheckMode : ProgramUpdateCheckMode::Disabled; + std::optional mode = enabled ? m_Settings.m_ReleaseChannel : ReleaseChannel::None; if (Combo("##SetupFlow_UpdateCheckingMode", mode)) - m_Settings.m_ProgramUpdateCheckMode = mode; + m_Settings.m_ReleaseChannel = mode; - if (m_Settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Disabled) + if (m_Settings.m_ReleaseChannel == ReleaseChannel::None) ImGui::TextFmt("You can always check for updates manually via the Help menu."); } ImGui::Unindent(); @@ -64,9 +65,9 @@ auto NetworkSettingsPage::OnDraw(const DrawState& ds) -> OnDrawResult void NetworkSettingsPage::Init(const Settings& settings) { m_Settings.m_AllowInternetUsage = settings.m_AllowInternetUsage.value_or(true); - m_Settings.m_ProgramUpdateCheckMode = settings.m_ProgramUpdateCheckMode; - if (m_Settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Unknown) - m_Settings.m_ProgramUpdateCheckMode = ProgramUpdateCheckMode::Releases; + m_Settings.m_ReleaseChannel = settings.m_ReleaseChannel; + if (!m_Settings.m_ReleaseChannel.has_value()) + m_Settings.m_ReleaseChannel = ReleaseChannel::Public; } bool NetworkSettingsPage::CanCommit() const @@ -77,5 +78,5 @@ bool NetworkSettingsPage::CanCommit() const void NetworkSettingsPage::Commit(Settings& settings) { settings.m_AllowInternetUsage = m_Settings.m_AllowInternetUsage; - settings.m_ProgramUpdateCheckMode = m_Settings.m_ProgramUpdateCheckMode; + settings.m_ReleaseChannel = m_Settings.m_ReleaseChannel; } diff --git a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h index 4ad30076..ce92b718 100644 --- a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h +++ b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h @@ -7,10 +7,12 @@ namespace tf2_bot_detector { + enum class ReleaseChannel; + class NetworkSettingsPage final : public ISetupFlowPage { public: - bool ValidateSettings(const Settings& settings) const override; + ValidateSettingsResult ValidateSettings(const Settings& settings) const override; OnDrawResult OnDraw(const DrawState& ds) override; void Init(const Settings& settings) override; bool CanCommit() const override; @@ -20,7 +22,7 @@ namespace tf2_bot_detector struct { std::optional m_AllowInternetUsage; - ProgramUpdateCheckMode m_ProgramUpdateCheckMode = ProgramUpdateCheckMode::Unknown; + std::optional m_ReleaseChannel; } m_Settings; }; diff --git a/tf2_bot_detector/SetupFlow/SetupFlow.cpp b/tf2_bot_detector/SetupFlow/SetupFlow.cpp index 2e913bf5..5c4263bb 100644 --- a/tf2_bot_detector/SetupFlow/SetupFlow.cpp +++ b/tf2_bot_detector/SetupFlow/SetupFlow.cpp @@ -20,11 +20,17 @@ using namespace std::string_literals; using namespace std::string_view_literals; using namespace tf2_bot_detector; +namespace tf2_bot_detector +{ + std::unique_ptr CreateUpdateCheckPage(); +} + SetupFlow::SetupFlow() { m_Pages.push_back(std::make_unique()); - m_Pages.push_back(std::make_unique()); m_Pages.push_back(std::make_unique()); + m_Pages.push_back(CreateUpdateCheckPage()); + m_Pages.push_back(std::make_unique()); //m_Pages.push_back(std::make_unique()); m_Pages.push_back(std::make_unique()); m_Pages.push_back(std::make_unique()); @@ -48,7 +54,7 @@ void SetupFlow::GetPageState(const Settings& settings, size_t& currentPage, bool for (size_t i = 0; i < m_Pages.size(); i++) { - if (m_Pages[i]->ValidateSettings(settings)) + if (m_Pages[i]->ValidateSettings(settings) != ISetupFlowPage::ValidateSettingsResult::TriggerOpen) continue; if (currentPage == INVALID_PAGE || i < currentPage) @@ -85,7 +91,7 @@ bool SetupFlow::OnDraw(Settings& settings, const ISetupFlowPage::DrawState& ds) if (m_ActivePage != lastPage) { - assert(!page->ValidateSettings(settings)); + assert(page->ValidateSettings(settings) == ISetupFlowPage::ValidateSettingsResult::TriggerOpen); page->Init(settings); } diff --git a/tf2_bot_detector/SetupFlow/TF2CommandLinePage.cpp b/tf2_bot_detector/SetupFlow/TF2CommandLinePage.cpp index 8eade3d0..342d03da 100644 --- a/tf2_bot_detector/SetupFlow/TF2CommandLinePage.cpp +++ b/tf2_bot_detector/SetupFlow/TF2CommandLinePage.cpp @@ -79,14 +79,14 @@ void TF2CommandLinePage::Data::TryUpdateCmdlineArgs() } } -bool TF2CommandLinePage::ValidateSettings(const Settings& settings) const +auto TF2CommandLinePage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult { if (!Processes::IsTF2Running()) - return false; + return ValidateSettingsResult::TriggerOpen; if (!m_Data.m_CommandLineArgs->IsPopulated()) - return false; + return ValidateSettingsResult::TriggerOpen; - return true; + return ValidateSettingsResult::Success; } auto TF2CommandLinePage::TF2CommandLine::Parse(const std::string_view& cmdLine) -> TF2CommandLine diff --git a/tf2_bot_detector/SetupFlow/TF2CommandLinePage.h b/tf2_bot_detector/SetupFlow/TF2CommandLinePage.h index aa334d6a..a28fe6d7 100644 --- a/tf2_bot_detector/SetupFlow/TF2CommandLinePage.h +++ b/tf2_bot_detector/SetupFlow/TF2CommandLinePage.h @@ -16,7 +16,7 @@ namespace tf2_bot_detector class TF2CommandLinePage final : public ISetupFlowPage { public: - bool ValidateSettings(const Settings& settings) const override; + ValidateSettingsResult ValidateSettings(const Settings& settings) const override; OnDrawResult OnDraw(const DrawState& ds) override; void Init(const Settings& settings) override; diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp new file mode 100644 index 00000000..d0ad8e0b --- /dev/null +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -0,0 +1,159 @@ +#include "Config/Settings.h" +#include "SetupFlow/ISetupFlowPage.h" +#include "UI/ImGui_TF2BotDetector.h" +#include "Log.h" +#include "ReleaseChannel.h" +#include "UpdateManager.h" + +#include + +using namespace tf2_bot_detector; + +namespace +{ + class UpdateCheckPage final : public ISetupFlowPage + { + public: + ValidateSettingsResult ValidateSettings(const Settings& settings) const override; + OnDrawResult OnDraw(const DrawState& ds) override; + void Init(const Settings& settings) override; + + bool CanCommit() const override; + void Commit(Settings& settings); + + bool WantsContinueButton() const override { return false; } + + private: + UpdateStatus m_LastUpdateStatus = UpdateStatus::Unknown; + bool m_HasCheckedForUpdate = false; + bool m_UpdateButtonPressed = false; + }; + + auto UpdateCheckPage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult + { + if (settings.GetHTTPClient() && !m_HasCheckedForUpdate) + return ValidateSettingsResult::TriggerOpen; + + return ValidateSettingsResult::Success; + } + + auto UpdateCheckPage::OnDraw(const DrawState& ds) -> OnDrawResult + { + m_HasCheckedForUpdate = true; + + ImGui::NewLine(); + + const UpdateStatus updateStatus = m_LastUpdateStatus = ds.m_UpdateManager->GetUpdateStatus(); + const IAvailableUpdate* update = ds.m_UpdateManager->GetAvailableUpdate(); + + bool drawContinueWithoutUpdating = false; + + switch (updateStatus) + { + case UpdateStatus::Unknown: + ImGui::TextFmt({ 1, 1, 0, 1 }, "Unknown"); + drawContinueWithoutUpdating = true; + break; + + case UpdateStatus::UpdateCheckDisabled: + ImGui::TextFmt("Automatic update checks disabled by user"); + return OnDrawResult::EndDrawing; + case UpdateStatus::InternetAccessDisabled: + ImGui::TextFmt("Internet connectivity disabled by user"); + return OnDrawResult::EndDrawing; + + case UpdateStatus::CheckQueued: + ImGui::TextFmt("Update check queued..."); + break; + case UpdateStatus::Checking: + ImGui::TextFmt("Checking for updates..."); + break; + + case UpdateStatus::CheckFailed: + ImGui::TextFmt({ 1, 1, 0, 1 }, "Update check failed"); + drawContinueWithoutUpdating = true; + ImGui::NewLine(); + break; + case UpdateStatus::UpToDate: + ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, + ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public)); + return OnDrawResult::EndDrawing; + + case UpdateStatus::UpdateAvailable: + { + ImGui::TextFmt({ 0, 1, 1, 1 }, "Update available: v{} {:v} (current version v{})", + update->GetVersion(), update->GetReleaseChannel(), VERSION); + drawContinueWithoutUpdating = true; + + ImGui::NewLine(); + + ImGui::EnabledSwitch(update->CanSelfUpdate(), [&] + { + ImGui::EnabledSwitch(!m_UpdateButtonPressed, [&] + { + if (ImGui::Button("Update Now")) + { + update->BeginSelfUpdate(); + } + + }, "Update in progress..."); + + }, "Self-update not currently available. Visit GitHub to download and install the new version."); + + ImGui::SameLine(); + + break; + } + + case UpdateStatus::Updating: + ImGui::TextFmt("Updating..."); + break; + + case UpdateStatus::UpdateFailed: + ImGui::TextFmt({ 1, 0, 0, 1 }, "Update failed"); + break; + case UpdateStatus::UpdateSuccess: + ImGui::TextFmt({ 0, 1, 0, 1 }, "Update succeeded. Restart TF2 Bot Detector to apply."); + break; + + default: + LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown UpdateStatus {}", updateStatus); + ImGui::TextFmt({ 1, 0, 0, 1 }, "Unexpected({})", updateStatus); + break; + } + + if (drawContinueWithoutUpdating) + { + if (ImGui::Button("Continue without updating >")) + return OnDrawResult::EndDrawing; + } + + return OnDrawResult::ContinueDrawing; + } + + void UpdateCheckPage::Init(const Settings& settings) + { + m_UpdateButtonPressed = false; + } + + bool UpdateCheckPage::CanCommit() const + { + return mh::none_eq(m_LastUpdateStatus + , UpdateStatus::CheckQueued + , UpdateStatus::Checking + , UpdateStatus::Updating + ); + } + + void UpdateCheckPage::Commit(Settings& settings) + { + } +} + +namespace tf2_bot_detector +{ + std::unique_ptr CreateUpdateCheckPage() + { + return std::make_unique(); + } +} diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp index e93b7c4f..fec7be14 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp @@ -1,6 +1,5 @@ #include "ImGui_TF2BotDetector.h" #include "Util/PathUtils.h" -#include "CachedVariable.h" #include "Clock.h" #include "Networking/NetworkHelpers.h" #include "Config/Settings.h" @@ -8,8 +7,10 @@ #include "SteamID.h" #include "Platform/Platform.h" #include "Version.h" +#include "ReleaseChannel.h" #include +#include #include #include @@ -450,7 +451,7 @@ static bool InputTextIPv4(std::string& addr, bool requireValid) bool tf2_bot_detector::InputTextLocalIPOverride(const std::string_view& label_id, std::string& ip, bool requireValid) { - static CachedVariable s_AutodetectedValue(1s, &Networking::GetLocalIP); + static mh::cached_variable s_AutodetectedValue(1s, &Networking::GetLocalIP); static const auto IsValid = [](std::string value) -> bool { @@ -463,7 +464,7 @@ bool tf2_bot_detector::InputTextLocalIPOverride(const std::string_view& label_id return InputTextIPv4(ip, true); }; - return OverrideControl(label_id, ip, s_AutodetectedValue.GetAndUpdate(), IsValid, InputFunc); + return OverrideControl(label_id, ip, s_AutodetectedValue.get(), IsValid, InputFunc); } bool tf2_bot_detector::InputTextSteamAPIKey(const char* label_id, std::string& key, bool requireValid) @@ -520,10 +521,11 @@ bool tf2_bot_detector::InputTextSteamAPIKey(const char* label_id, std::string& k return false; } -bool tf2_bot_detector::Combo(const char* label_id, ProgramUpdateCheckMode& mode) +bool tf2_bot_detector::Combo(const char* label_id, std::optional& mode) { const char* friendlyText = ""; static constexpr char FRIENDLY_TEXT_DISABLED[] = "Disable automatic update checks"; + static constexpr char FRIENDLY_TEXT_NIGHTLY[] = "Notify about new every builds (unstable)"; static constexpr char FRIENDLY_TEXT_PREVIEW[] = "Notify about new preview releases"; static constexpr char FRIENDLY_TEXT_STABLE[] = "Notify about new stable releases"; @@ -535,18 +537,25 @@ bool tf2_bot_detector::Combo(const char* label_id, ProgramUpdateCheckMode& mode) mode = ProgramUpdateCheckMode::Previews; #endif - switch (mode) + if (mode.has_value()) { - case ProgramUpdateCheckMode::Disabled: friendlyText = FRIENDLY_TEXT_DISABLED; break; - case ProgramUpdateCheckMode::Previews: friendlyText = FRIENDLY_TEXT_PREVIEW; break; - case ProgramUpdateCheckMode::Releases: friendlyText = FRIENDLY_TEXT_STABLE; break; - case ProgramUpdateCheckMode::Unknown: friendlyText = "Select an option"; break; + switch (*mode) + { + case ReleaseChannel::None: friendlyText = FRIENDLY_TEXT_DISABLED; break; + case ReleaseChannel::Nightly: friendlyText = FRIENDLY_TEXT_NIGHTLY; break; + case ReleaseChannel::Preview: friendlyText = FRIENDLY_TEXT_PREVIEW; break; + case ReleaseChannel::Public: friendlyText = FRIENDLY_TEXT_STABLE; break; + } + } + else + { + friendlyText = "Select an option"; } if (ImGui::BeginCombo(label_id, friendlyText)) { if (ImGui::Selectable(FRIENDLY_TEXT_DISABLED)) - mode = ProgramUpdateCheckMode::Disabled; + mode = ReleaseChannel::None; #if 0 ImGui::EnabledSwitch(allowReleases, [&] @@ -560,11 +569,13 @@ bool tf2_bot_detector::Combo(const char* label_id, ProgramUpdateCheckMode& mode) }, "Since you are using a preview build, you will always be notified of new previews."); #else if (ImGui::Selectable(FRIENDLY_TEXT_STABLE)) - mode = ProgramUpdateCheckMode::Releases; + mode = ReleaseChannel::Public; #endif if (ImGui::Selectable(FRIENDLY_TEXT_PREVIEW)) - mode = ProgramUpdateCheckMode::Previews; + mode = ReleaseChannel::Preview; + if (ImGui::Selectable(FRIENDLY_TEXT_NIGHTLY)) + mode = ReleaseChannel::Nightly; ImGui::EndCombo(); } diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.h b/tf2_bot_detector/UI/ImGui_TF2BotDetector.h index b238e1e3..acb61083 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.h +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.h @@ -185,7 +185,7 @@ namespace ImGui namespace tf2_bot_detector { - enum class ProgramUpdateCheckMode; + enum class ReleaseChannel; class SteamID; bool InputTextSteamIDOverride(const char* label_id, SteamID& steamID, bool requireValid = true); @@ -194,7 +194,7 @@ namespace tf2_bot_detector bool InputTextSteamDirOverride(const std::string_view& label_id, std::filesystem::path& path, bool requireValid = false); bool InputTextLocalIPOverride(const std::string_view& label_id, std::string& ip, bool requireValid = false); bool InputTextSteamAPIKey(const char* label_id, std::string& key, bool requireValid = false); - bool Combo(const char* label_id, ProgramUpdateCheckMode& mode); + bool Combo(const char* label_id, std::optional& mode); bool AutoLaunchTF2Checkbox(bool& value); } diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index acda3ad6..e71b0e9d 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -11,7 +11,9 @@ #include "Filesystem.h" #include "Log.h" #include "IPlayer.h" +#include "ReleaseChannel.h" #include "TextureManager.h" +#include "UpdateManager.h" #include "Util/PathUtils.h" #include "Version.h" @@ -37,27 +39,13 @@ using namespace std::chrono_literals; using namespace std::string_literals; using namespace std::string_view_literals; -static std::string_view GetVersionSuffix() -{ - const auto channel = Platform::GetPlatformUpdateChannel(); - if (!channel.has_value()) - return {}; - - switch (*channel) - { - default: return {}; - case ReleaseChannel::Public: return " (Stable)"sv; - case ReleaseChannel::Preview: return " (Preview)"sv; - case ReleaseChannel::Nightly: return " (Nightly)"sv; - } -} - MainWindow::MainWindow() : - ImGuiDesktop::Window(800, 600, mh::fmtstr<128>("TF2 Bot Detector v{}{}", VERSION, GetVersionSuffix()).c_str()), + ImGuiDesktop::Window(800, 600, mh::fmtstr<128>("TF2 Bot Detector v{}", VERSION).c_str()), m_WorldState(IWorldState::Create(m_Settings)), m_ActionManager(IRCONActionManager::Create(m_Settings, GetWorld())), m_TextureManager(CreateTextureManager()), - m_BaseTextures(IBaseTextures::Create(*m_TextureManager)) + m_BaseTextures(IBaseTextures::Create(*m_TextureManager)), + m_UpdateManager(IUpdateManager::Create(m_Settings)) { m_TextureManager = CreateTextureManager(); @@ -307,10 +295,10 @@ void MainWindow::OnDrawSettingsPopup() } ImGui::NewLine(); - if (auto mode = enabled ? m_Settings.m_ProgramUpdateCheckMode : ProgramUpdateCheckMode::Disabled; + if (auto mode = enabled ? m_Settings.m_ReleaseChannel : ReleaseChannel::None; Combo("Automatic update checking", mode)) { - m_Settings.m_ProgramUpdateCheckMode = mode; + m_Settings.m_ReleaseChannel = mode; m_Settings.SaveFile(); } }, "Requires \"Allow internet connectivity\""); @@ -338,96 +326,13 @@ void MainWindow::OnDrawUpdateCheckPopup() ImGui::OpenPopup(POPUP_NAME); s_Open = true; } - - ImGui::SetNextWindowSize({ 500, 300 }, ImGuiCond_Appearing); - if (ImGui::BeginPopupModal(POPUP_NAME, &s_Open, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::PushTextWrapPos(); - ImGui::TextFmt("You have chosen to disable internet connectivity for TF2 Bot Detector. You can still manually check for updates below."); - ImGui::TextFmt({ 1, 1, 0, 1 }, "Reminder: if you use antivirus software, connecting to the internet may trigger warnings."); - - ImGui::EnabledSwitch(!m_UpdateInfo.valid(), [&] - { - if (ImGui::Button("Check for updates")) - GetUpdateInfo(); - }); - - ImGui::NewLine(); - - if (mh::is_future_ready(m_UpdateInfo)) - { - auto& updateInfo = m_UpdateInfo.get(); - - if (updateInfo.IsUpToDate()) - { - ImGui::TextFmt({ 0.1f, 1, 0.1f, 1 }, "You are already running the latest version of TF2 Bot Detector."); - } - else if (updateInfo.IsPreviewAvailable()) - { - ImGui::TextFmt("There is a new preview version available."); - if (ImGui::Button("View on Github")) - Shell::OpenURL(updateInfo.m_Preview->m_URL); - } - else if (updateInfo.IsReleaseAvailable()) - { - ImGui::TextFmt("There is a new stable version available."); - if (ImGui::Button("View on Github")) - Shell::OpenURL(updateInfo.m_Stable->m_URL); - } - else if (updateInfo.IsError()) - { - ImGui::TextFmt({ 1, 0, 0, 1 }, "There was an error checking for updates."); - } - } - else if (m_UpdateInfo.valid()) - { - ImGui::TextFmt("Checking for updates..."); - } - else - { - ImGui::TextFmt("Press \"Check for updates\" to check Github for a newer version."); - } - - ImGui::EndPopup(); - } } void MainWindow::OpenUpdateCheckPopup() { - m_NotifyOnUpdateAvailable = false; m_UpdateCheckPopupOpen = true; } -void MainWindow::OnDrawUpdateAvailablePopup() -{ - static constexpr char POPUP_NAME[] = "Update Available##Popup"; - - static bool s_Open = false; - if (m_UpdateAvailablePopupOpen) - { - m_UpdateAvailablePopupOpen = false; - ImGui::OpenPopup(POPUP_NAME); - s_Open = true; - } - - if (ImGui::BeginPopupModal(POPUP_NAME, &s_Open, ImGuiWindowFlags_AlwaysAutoResize)) - { - ImGui::TextFmt("There is a new{} version of TF2 Bot Detector available for download.", - (m_UpdateInfo.get().IsPreviewAvailable() ? " preview" : "")); - - if (ImGui::Button("View on Github")) - Shell::OpenURL(m_UpdateInfo.get().GetURL()); - - ImGui::EndPopup(); - } -} - -void MainWindow::OpenUpdateAvailablePopup() -{ - m_NotifyOnUpdateAvailable = false; - m_UpdateAvailablePopupOpen = true; -} - void MainWindow::OnDrawAboutPopup() { static constexpr char POPUP_NAME[] = "About##Popup"; @@ -598,13 +503,13 @@ void MainWindow::OnDrawServerStats() void MainWindow::OnDraw() { OnDrawSettingsPopup(); - OnDrawUpdateAvailablePopup(); OnDrawUpdateCheckPopup(); OnDrawAboutPopup(); { ISetupFlowPage::DrawState ds; ds.m_ActionManager = &GetActionManager(); + ds.m_UpdateManager = m_UpdateManager.get(); ds.m_Settings = &m_Settings; if (m_SetupFlow.OnDraw(m_Settings, ds)) @@ -785,40 +690,6 @@ void MainWindow::OnDrawMenuBar() static const mh::fmtstr<128> VERSION_STRING_LABEL("Version: {}", VERSION); ImGui::MenuItem(VERSION_STRING_LABEL.c_str(), nullptr, false, false); - if (m_Settings.m_AllowInternetUsage.value_or(false)) - { - auto newVersion = GetUpdateInfo(); - if (!newVersion) - { - ImGui::MenuItem("Checking for new version...", nullptr, nullptr, false); - } - else if (newVersion->IsUpToDate()) - { - ImGui::MenuItem("Up to date!", nullptr, nullptr, false); - } - else if (newVersion->IsReleaseAvailable()) - { - ImGuiDesktop::ScopeGuards::TextColor green({ 0, 1, 0, 1 }); - if (ImGui::MenuItem("A new version is available")) - Shell::OpenURL(newVersion->m_Stable->m_URL); - } - else if (newVersion->IsPreviewAvailable()) - { - if (ImGui::MenuItem("A new preview is available")) - Shell::OpenURL(newVersion->m_Preview->m_URL); - } - else - { - assert(newVersion->IsError()); - ImGui::MenuItem("Error occurred checking for new version.", nullptr, nullptr, false); - } - } - else - { - if (ImGui::MenuItem("Check for updates...")) - OpenUpdateCheckPopup(); - } - ImGui::Separator(); if (ImGui::MenuItem("About TF2 Bot Detector")) @@ -828,46 +699,6 @@ void MainWindow::OnDrawMenuBar() } } -GithubAPI::NewVersionResult* MainWindow::GetUpdateInfo() -{ - if (!m_UpdateInfo.valid()) - { - if (auto client = m_Settings.GetHTTPClient()) - m_UpdateInfo = GithubAPI::CheckForNewVersion(*client); - else - return nullptr; - } - - if (mh::is_future_ready(m_UpdateInfo)) - return const_cast(&m_UpdateInfo.get()); - - return nullptr; -} - -void MainWindow::HandleUpdateCheck() -{ - if (!m_NotifyOnUpdateAvailable) - return; - - if (!m_Settings.m_AllowInternetUsage.value_or(false)) - return; - - const bool checkPreviews = m_Settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Previews; - const bool checkReleases = checkPreviews || m_Settings.m_ProgramUpdateCheckMode == ProgramUpdateCheckMode::Releases; - if (!checkPreviews && !checkReleases) - return; - - auto result = GetUpdateInfo(); - if (!result) - return; - - if ((result->IsPreviewAvailable() && checkPreviews) || - (result->IsReleaseAvailable() && checkReleases)) - { - OpenUpdateAvailablePopup(); - } -} - void MainWindow::PostSetupFlowState::OnUpdateDiscord() { #ifdef TF2BD_ENABLE_DISCORD_INTEGRATION @@ -892,8 +723,7 @@ void MainWindow::OnUpdate() return; GetWorld().Update(); - - HandleUpdateCheck(); + m_UpdateManager->Update(); if (m_Settings.m_Unsaved.m_RCONClient) m_Settings.m_Unsaved.m_RCONClient->set_logging(m_Settings.m_Logging.m_RCONPackets); diff --git a/tf2_bot_detector/UI/MainWindow.h b/tf2_bot_detector/UI/MainWindow.h index 5d8f3d04..fefca2c3 100644 --- a/tf2_bot_detector/UI/MainWindow.h +++ b/tf2_bot_detector/UI/MainWindow.h @@ -28,11 +28,12 @@ struct ImVec4; namespace tf2_bot_detector { + class IBaseTextures; class IConsoleLine; class IConsoleLineListener; class ITexture; class ITextureManager; - class IBaseTextures; + class IUpdateManager; class MainWindow final : public ImGuiDesktop::Window, IConsoleLineListener, BaseWorldEventListener { @@ -72,21 +73,12 @@ namespace tf2_bot_detector bool m_UpdateCheckPopupOpen = false; void OpenUpdateCheckPopup(); - void OnDrawUpdateAvailablePopup(); - bool m_UpdateAvailablePopupOpen = false; - void OpenUpdateAvailablePopup(); - void OnDrawAboutPopup(); bool m_AboutPopupOpen = false; void OpenAboutPopup() { m_AboutPopupOpen = true; } void GenerateDebugReport(); - GithubAPI::NewVersionResult* GetUpdateInfo(); - std::shared_future m_UpdateInfo; - bool m_NotifyOnUpdateAvailable = true; - void HandleUpdateCheck(); - void OnUpdate() override; bool IsSleepingEnabled() const override; @@ -150,6 +142,9 @@ namespace tf2_bot_detector time_point_t m_LastServerPingSample{}; Settings m_Settings; + + std::unique_ptr m_UpdateManager; + SetupFlow m_SetupFlow; std::unique_ptr m_WorldState; diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp new file mode 100644 index 00000000..ff30bc32 --- /dev/null +++ b/tf2_bot_detector/UpdateManager.cpp @@ -0,0 +1,317 @@ +#include "UpdateManager.h" +#include "Config/Settings.h" +#include "Networking/GithubAPI.h" +#include "Networking/HTTPClient.h" +#include "Platform/Platform.h" +#include "Log.h" +#include "ReleaseChannel.h" + +#include +#include +#include + +#include + +using namespace tf2_bot_detector; + +namespace +{ + class BaseUpdateCheck + { + public: + BaseUpdateCheck(ReleaseChannel rc, const HTTPClient& client); + virtual ~BaseUpdateCheck() = default; + + virtual void Update() = 0; + virtual UpdateStatus GetUpdateStatus() const = 0; + const IAvailableUpdate* GetAvailableUpdate() const; + + protected: + virtual bool CanSelfUpdate() const = 0; + virtual void BeginSelfUpdateImpl() const = 0; + const HTTPClient& GetHTTPClient() const { return *m_Client; } + + struct AvailableUpdate final : public IAvailableUpdate + { + AvailableUpdate(BaseUpdateCheck& parent); + + ReleaseChannel m_ReleaseChannel{}; + ReleaseChannel GetReleaseChannel() const override final { return m_ReleaseChannel; } + + Version m_Version{}; + Version GetVersion() const override final { return m_Version; } + + bool CanSelfUpdate() const override { return m_Parent.CanSelfUpdate(); } + void BeginSelfUpdate() const override final; + + private: + BaseUpdateCheck& m_Parent; + + } m_AvailableUpdate; + + private: + std::shared_ptr m_Client; + }; + + BaseUpdateCheck::BaseUpdateCheck(ReleaseChannel rc, const HTTPClient& client) : + m_AvailableUpdate(*this), + m_Client(client.shared_from_this()) + { + m_AvailableUpdate.m_ReleaseChannel = rc; + } + + const IAvailableUpdate* BaseUpdateCheck::GetAvailableUpdate() const + { + return m_AvailableUpdate.m_Version > VERSION ? &m_AvailableUpdate : nullptr; + } + + BaseUpdateCheck::AvailableUpdate::AvailableUpdate(BaseUpdateCheck& parent) : + m_Parent(parent) + { + } + + void BaseUpdateCheck::AvailableUpdate::BeginSelfUpdate() const + { + if (!CanSelfUpdate()) + throw std::logic_error("BeginSelfUpdate() called when CanSelfUpdate() returned false"); + + return m_Parent.BeginSelfUpdateImpl(); + } +} + +namespace +{ + class PlatformUpdateCheck final : public BaseUpdateCheck + { + public: + PlatformUpdateCheck(ReleaseChannel rc, const HTTPClient& client); + + void Update() override; + UpdateStatus GetUpdateStatus() const override; + + protected: + bool CanSelfUpdate() const override { return true; } + void BeginSelfUpdateImpl() const override; + + private: + std::variant, std::exception_ptr> m_IsPlatformUpdateAvailable; + std::future> m_CheckForPlatformUpdateTask; + }; + + PlatformUpdateCheck::PlatformUpdateCheck(ReleaseChannel rc, const HTTPClient& client) : + BaseUpdateCheck(rc, client) + { + m_CheckForPlatformUpdateTask = Platform::CheckForPlatformUpdate(rc, client); + } + + void PlatformUpdateCheck::Update() + { + if (mh::is_future_ready(m_CheckForPlatformUpdateTask)) + { + try + { + auto version = m_CheckForPlatformUpdateTask.get(); + m_IsPlatformUpdateAvailable = version; + + if (version) + m_AvailableUpdate.m_Version = *version; + } + catch (const std::exception& e) + { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to retrieve result of platform update availability check"); + m_IsPlatformUpdateAvailable = std::current_exception(); + } + } + } + + UpdateStatus PlatformUpdateCheck::GetUpdateStatus() const + { + if (std::holds_alternative(m_IsPlatformUpdateAvailable)) + return UpdateStatus::Checking; + else if (const auto* value = std::get_if>(&m_IsPlatformUpdateAvailable)) + return *value ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; + else if (std::holds_alternative(m_IsPlatformUpdateAvailable)) + return UpdateStatus::CheckFailed; + else + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown variant index {}", m_IsPlatformUpdateAvailable.index()); + return UpdateStatus::CheckFailed; + } + } + + void PlatformUpdateCheck::BeginSelfUpdateImpl() const + { + auto update = GetAvailableUpdate(); + if (!update) + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "BeginSelfUpdateImpl() called when GetAvailableUpdate() returned nullptr"); + return; + } + + Platform::BeginPlatformUpdate(update->GetReleaseChannel(), GetHTTPClient()); + } +} + +namespace +{ + class PortableUpdateCheck final : public BaseUpdateCheck + { + public: + PortableUpdateCheck(ReleaseChannel rc, const HTTPClient& client); + + void Update() override; + UpdateStatus GetUpdateStatus() const override; + + protected: + bool CanSelfUpdate() const override { return false; } + void BeginSelfUpdateImpl() const override; + + private: + std::variant< + std::future, + GithubAPI::NewVersionResult, + std::exception_ptr> + m_NewVersionResult; + }; + + PortableUpdateCheck::PortableUpdateCheck(ReleaseChannel rc, const HTTPClient& client) : + BaseUpdateCheck(rc, client) + { + m_NewVersionResult = GithubAPI::CheckForNewVersion(client); + } + + void PortableUpdateCheck::Update() + { + if (auto future = std::get_if>(&m_NewVersionResult); + future && mh::is_future_ready(*future)) + { + try + { + auto versionCheckResult = future->get(); + m_NewVersionResult = versionCheckResult; + + if (m_AvailableUpdate.m_ReleaseChannel == ReleaseChannel::Preview && + versionCheckResult.IsPreviewAvailable()) + { + m_AvailableUpdate.m_Version = versionCheckResult.m_Preview->m_Version; + } + else if (mh::any_eq(m_AvailableUpdate.m_ReleaseChannel, ReleaseChannel::Preview, ReleaseChannel::Public) && + versionCheckResult.IsReleaseAvailable()) + { + m_AvailableUpdate.m_Version = versionCheckResult.m_Stable->m_Version; + } + } + catch (const std::exception& e) + { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, + "Failed to retrieve result of platform update availability check"); + m_NewVersionResult = std::current_exception(); + } + } + } + + UpdateStatus PortableUpdateCheck::GetUpdateStatus() const + { + if (std::holds_alternative>(m_NewVersionResult)) + { + return UpdateStatus::Checking; + } + else if (std::holds_alternative(m_NewVersionResult)) + { + return m_AvailableUpdate.m_Version > VERSION ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; + } + else if (std::holds_alternative(m_NewVersionResult)) + { + return UpdateStatus::CheckFailed; + } + + LogError(MH_SOURCE_LOCATION_CURRENT(), "{}", UpdateStatus::InternalError_UnknownVariantState); + return UpdateStatus::InternalError_UnknownVariantState; + } + + void PortableUpdateCheck::BeginSelfUpdateImpl() const + { + throw std::logic_error("Portable self-updating not currently supported"); + } +} + +namespace +{ + class UpdateManager final : public IUpdateManager + { + public: + UpdateManager(const Settings& settings); + + void Update() override; + UpdateStatus GetUpdateStatus() const override; + + const IAvailableUpdate* GetAvailableUpdate() const override; + + void QueueUpdateCheck() override { m_IsUpdateQueued = true; } + + private: + const Settings& m_Settings; + + bool m_IsUpdateQueued = true; + bool m_IsInstalled; + std::unique_ptr m_UpdateCheck; + }; + + UpdateManager::UpdateManager(const Settings& settings) : + m_Settings(settings), + m_IsInstalled(Platform::IsInstalled()) + { + } + + void UpdateManager::Update() + { + if (m_IsUpdateQueued) + { + auto client = m_Settings.GetHTTPClient(); + if (client && (m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::None) != ReleaseChannel::None)) + { + if (m_IsInstalled) + m_UpdateCheck = std::make_unique(*m_Settings.m_ReleaseChannel, *client); + else + m_UpdateCheck = std::make_unique(*m_Settings.m_ReleaseChannel, *client); + + m_IsUpdateQueued = false; + } + } + + if (m_UpdateCheck) + m_UpdateCheck->Update(); + } + + UpdateStatus UpdateManager::GetUpdateStatus() const + { + if (m_IsUpdateQueued) + { + if (m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::None) == ReleaseChannel::None) + return UpdateStatus::UpdateCheckDisabled; + else if (!m_Settings.GetHTTPClient()) + return UpdateStatus::InternetAccessDisabled; + else + return UpdateStatus::CheckQueued; + } + + if (m_UpdateCheck) + return m_UpdateCheck->GetUpdateStatus(); + + return UpdateStatus::InternalError_UpdateCheckNull; + } + + const IAvailableUpdate* UpdateManager::GetAvailableUpdate() const + { + if (m_UpdateCheck) + return m_UpdateCheck->GetAvailableUpdate(); + + return nullptr; + } +} + +std::unique_ptr tf2_bot_detector::IUpdateManager::Create(const Settings& settings) +{ + return std::make_unique(settings); +} diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h new file mode 100644 index 00000000..ab08c743 --- /dev/null +++ b/tf2_bot_detector/UpdateManager.h @@ -0,0 +1,83 @@ +#pragma once + +#include "Version.h" + +#include + +#include + +namespace tf2_bot_detector +{ + enum class ReleaseChannel; + class Settings; + + class IAvailableUpdate + { + public: + virtual ~IAvailableUpdate() = default; + + virtual ReleaseChannel GetReleaseChannel() const = 0; + virtual Version GetVersion() const = 0; + + virtual bool CanSelfUpdate() const = 0; + virtual void BeginSelfUpdate() const = 0; + }; + + enum class UpdateStatus + { + Unknown = 0, + + InternalError_UpdateCheckNull, + InternalError_UnknownVariantState, + + UpdateCheckDisabled, + InternetAccessDisabled, + + CheckQueued, + Checking, + + CheckFailed, + UpToDate, + UpdateAvailable, + + Updating, + + UpdateFailed, + UpdateSuccess, + }; + + class IUpdateManager + { + public: + virtual ~IUpdateManager() = default; + + static std::unique_ptr Create(const Settings& settings); + + virtual void QueueUpdateCheck() = 0; + + virtual void Update() = 0; + virtual UpdateStatus GetUpdateStatus() const = 0; + + virtual const IAvailableUpdate* GetAvailableUpdate() const = 0; + }; +} + +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) + MH_ENUM_REFLECT_VALUE(Unknown) + + MH_ENUM_REFLECT_VALUE(InternalError_UpdateCheckNull) + + MH_ENUM_REFLECT_VALUE(UpdateCheckDisabled) + MH_ENUM_REFLECT_VALUE(InternetAccessDisabled) + + MH_ENUM_REFLECT_VALUE(Checking) + + MH_ENUM_REFLECT_VALUE(CheckFailed) + MH_ENUM_REFLECT_VALUE(UpToDate) + MH_ENUM_REFLECT_VALUE(UpdateAvailable) + + MH_ENUM_REFLECT_VALUE(Updating) + + MH_ENUM_REFLECT_VALUE(UpdateFailed) + MH_ENUM_REFLECT_VALUE(UpdateSuccess) +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector/Util/JSONUtils.h b/tf2_bot_detector/Util/JSONUtils.h index 12c37d99..03d724ff 100644 --- a/tf2_bot_detector/Util/JSONUtils.h +++ b/tf2_bot_detector/Util/JSONUtils.h @@ -7,6 +7,27 @@ #include #include +namespace std +{ + template + void to_json(nlohmann::json& j, const optional& d) + { + if (d.has_value()) + j = *d; + else + j = nullptr; + } + + template + void from_json(const nlohmann::json& j, optional& d) + { + if (j.is_null()) + d.reset(); + else + j.get_to(d.emplace()); + } +} + namespace tf2_bot_detector { namespace detail diff --git a/tf2_bot_detector/Version.base.h b/tf2_bot_detector/Version.base.h index 70eebb7c..7fa3973e 100644 --- a/tf2_bot_detector/Version.base.h +++ b/tf2_bot_detector/Version.base.h @@ -1,27 +1,43 @@ #pragma once #include +#include +#include #include +#include namespace tf2_bot_detector { struct Version { + using value_type = uint16_t; + + constexpr Version() = default; + + explicit constexpr Version(value_type major, value_type minor, value_type patch = 0, value_type build = 0) : + m_Major(major), + m_Minor(minor), + m_Patch(patch), + m_Build(build) + { + } + + static std::optional Parse(const char* str); + auto operator<=>(const Version&) const = default; - int m_Major; - int m_Minor; - int m_Patch; - int m_Build; + value_type m_Major{}; + value_type m_Minor{}; + value_type m_Patch{}; + value_type m_Build{}; }; - static constexpr Version VERSION = - { - .m_Major = ${CMAKE_PROJECT_VERSION_MAJOR}, - .m_Minor = ${CMAKE_PROJECT_VERSION_MINOR}, - .m_Patch = ${CMAKE_PROJECT_VERSION_PATCH}, - .m_Build = ${CMAKE_PROJECT_VERSION_TWEAK}, - }; + static constexpr Version VERSION( + ${CMAKE_PROJECT_VERSION_MAJOR}, + ${CMAKE_PROJECT_VERSION_MINOR}, + ${CMAKE_PROJECT_VERSION_PATCH}, + ${CMAKE_PROJECT_VERSION_TWEAK} + ); } template diff --git a/tf2_bot_detector/Version.cpp b/tf2_bot_detector/Version.cpp new file mode 100644 index 00000000..c8643f2d --- /dev/null +++ b/tf2_bot_detector/Version.cpp @@ -0,0 +1,13 @@ +#include "Version.h" + +using namespace tf2_bot_detector; + +std::optional Version::Parse(const char* str) +{ + Version v{}; + const auto count = std::sscanf(str, "%hi.%hi.%hi.%hi", &v.m_Major, &v.m_Minor, &v.m_Patch, &v.m_Build); + if (count < 2) + return std::nullopt; + + return v; +} From 83c5eefd61c77580cbc2aa4724cd1ee09d1d44ea Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 28 Aug 2020 22:18:25 -0700 Subject: [PATCH 083/161] Removed all forward declarations of ReleaseChannel due to "feature" in fmtlib that implicit casts enum classes to integers --- tf2_bot_detector/Config/Settings.h | 5 ++--- tf2_bot_detector/Platform/Platform.h | 2 +- tf2_bot_detector/ReleaseChannel.h | 1 + tf2_bot_detector/SetupFlow/NetworkSettingsPage.h | 3 +-- tf2_bot_detector/UI/ImGui_TF2BotDetector.h | 3 ++- tf2_bot_detector/UpdateManager.h | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tf2_bot_detector/Config/Settings.h b/tf2_bot_detector/Config/Settings.h index 4f6533f3..a33ca3b6 100644 --- a/tf2_bot_detector/Config/Settings.h +++ b/tf2_bot_detector/Config/Settings.h @@ -1,8 +1,9 @@ #pragma once +#include "Networking/HTTPClient.h" #include "ChatWrappers.h" #include "Clock.h" -#include "Networking/HTTPClient.h" +#include "ReleaseChannel.h" #include "SteamID.h" #include @@ -18,8 +19,6 @@ namespace srcon namespace tf2_bot_detector { - enum class ReleaseChannel; - void to_json(nlohmann::json& j, const ReleaseChannel& d); void from_json(const nlohmann::json& j, ReleaseChannel& d); diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 77ac379b..35573f85 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -1,5 +1,6 @@ #pragma once +#include "ReleaseChannel.h" #include "SteamID.h" #include "Version.h" @@ -11,7 +12,6 @@ namespace tf2_bot_detector { class HTTPClient; - enum class ReleaseChannel; inline namespace Platform { diff --git a/tf2_bot_detector/ReleaseChannel.h b/tf2_bot_detector/ReleaseChannel.h index ff82eb8b..87eaa9d3 100644 --- a/tf2_bot_detector/ReleaseChannel.h +++ b/tf2_bot_detector/ReleaseChannel.h @@ -4,6 +4,7 @@ namespace tf2_bot_detector { + // DO NOT FORWARD DECLARE (implicit cast in fmt::format from enum class -> int) enum class ReleaseChannel { None = -1, // Don't auto update diff --git a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h index ce92b718..561e0bec 100644 --- a/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h +++ b/tf2_bot_detector/SetupFlow/NetworkSettingsPage.h @@ -2,13 +2,12 @@ #include "Config/Settings.h" #include "ISetupFlowPage.h" +#include "ReleaseChannel.h" #include namespace tf2_bot_detector { - enum class ReleaseChannel; - class NetworkSettingsPage final : public ISetupFlowPage { public: diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.h b/tf2_bot_detector/UI/ImGui_TF2BotDetector.h index acb61083..1d907a46 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.h +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.h @@ -1,5 +1,7 @@ #pragma once +#include "ReleaseChannel.h" + #include #include #include @@ -185,7 +187,6 @@ namespace ImGui namespace tf2_bot_detector { - enum class ReleaseChannel; class SteamID; bool InputTextSteamIDOverride(const char* label_id, SteamID& steamID, bool requireValid = true); diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index ab08c743..5d004190 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -1,5 +1,6 @@ #pragma once +#include "ReleaseChannel.h" #include "Version.h" #include @@ -8,7 +9,6 @@ namespace tf2_bot_detector { - enum class ReleaseChannel; class Settings; class IAvailableUpdate From 9dcb08d14e27af364e30969bf20c144a31aa1c2d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 28 Aug 2020 23:31:04 -0700 Subject: [PATCH 084/161] minimum effort updater --- .github/workflows/ccpp.yml | 1 + CMakeLists.txt | 4 +- msix/New Text Document.txt | 0 msix/tf2-bot-detector.appinstaller_template | 27 ------ submodules/mh_stuff | 2 +- tf2_bot_detector/Config/Settings.cpp | 6 -- .../Platform/Windows/PlatformInstall.cpp | 9 +- tf2_bot_detector_common/CMakeLists.txt | 8 ++ .../include}/ReleaseChannel.h | 0 tf2_bot_detector_updater/CMakeLists.txt | 23 +++++ tf2_bot_detector_updater/Common.h | 42 +++++++++ tf2_bot_detector_updater/Update_MSIX.cpp | 82 +++++++++++++++++ tf2_bot_detector_updater/Update_MSIX.h | 6 ++ tf2_bot_detector_updater/main.cpp | 91 +++++++++++++++++++ 14 files changed, 258 insertions(+), 43 deletions(-) create mode 100644 msix/New Text Document.txt delete mode 100644 msix/tf2-bot-detector.appinstaller_template create mode 100644 tf2_bot_detector_common/CMakeLists.txt rename {tf2_bot_detector => tf2_bot_detector_common/include}/ReleaseChannel.h (100%) create mode 100644 tf2_bot_detector_updater/CMakeLists.txt create mode 100644 tf2_bot_detector_updater/Common.h create mode 100644 tf2_bot_detector_updater/Update_MSIX.cpp create mode 100644 tf2_bot_detector_updater/Update_MSIX.h create mode 100644 tf2_bot_detector_updater/main.cpp diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7e9b67df..739f62a3 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -127,6 +127,7 @@ jobs: uses: PazerOP/code-sign-action@v3 with: folder: '${{ steps.tf2bd_paths.outputs.build_dir }}' + recursive: true certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' diff --git a/CMakeLists.txt b/CMakeLists.txt index 72112f7e..fe7efdfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,8 @@ add_subdirectory(submodules/ValveFileVDF) add_subdirectory(submodules/SourceRCON) add_subdirectory(submodules/imgui_desktop) add_subdirectory(submodules/mh_stuff) +add_subdirectory(tf2_bot_detector_common) +add_subdirectory(tf2_bot_detector_updater) set(ENABLE_EXAMPLES off CACHE BOOL "Build examples" FORCE) add_subdirectory("submodules/cppcoro") @@ -157,7 +159,6 @@ target_sources(tf2_bot_detector PRIVATE "tf2_bot_detector/ModeratorLogic.cpp" "tf2_bot_detector/ModeratorLogic.h" "tf2_bot_detector/PlayerStatus.h" - "tf2_bot_detector/ReleaseChannel.h" "tf2_bot_detector/SteamID.cpp" "tf2_bot_detector/SteamID.h" "tf2_bot_detector/TextureManager.h" @@ -231,6 +232,7 @@ find_package(libzippp CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED) target_link_libraries(tf2_bot_detector PRIVATE + tf2_bot_detector_common imgui_desktop mh_stuff ValveFileVDF diff --git a/msix/New Text Document.txt b/msix/New Text Document.txt new file mode 100644 index 00000000..e69de29b diff --git a/msix/tf2-bot-detector.appinstaller_template b/msix/tf2-bot-detector.appinstaller_template deleted file mode 100644 index acff3391..00000000 --- a/msix/tf2-bot-detector.appinstaller_template +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - diff --git a/submodules/mh_stuff b/submodules/mh_stuff index 8e2af949..f3c5c88e 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit 8e2af9494a3967e7305ec25d127013dccb85679d +Subproject commit f3c5c88ef489745839106eea9c39f7ebb1759c2b diff --git a/tf2_bot_detector/Config/Settings.cpp b/tf2_bot_detector/Config/Settings.cpp index 02920dc5..1ae335e4 100644 --- a/tf2_bot_detector/Config/Settings.cpp +++ b/tf2_bot_detector/Config/Settings.cpp @@ -144,12 +144,6 @@ void GeneralSettings::SetSteamAPIKey(std::string key) void tf2_bot_detector::to_json(nlohmann::json& j, const ReleaseChannel& d) { - auto value = mh::enum_type::find_value_name(d); - if (value.empty()) - throw std::invalid_argument(mh::format("Unknown ReleaseChannel {}", d)); - - j = mh::tolower(value); - switch (d) { case ReleaseChannel::None: j = "disabled"; return; diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index 0d00acf5..a23f1870 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -92,14 +93,6 @@ void tf2_bot_detector::Platform::BeginPlatformUpdate(ReleaseChannel rc, const HT } else { - DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "App installer not found, attempting to install via API..."); - const Uri uri = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", rc); - IVector deps{ winrt::single_threaded_vector() }; - deps.Append(Uri(L"https://tf2bd-util.pazer.us/AppInstaller/vcredist.x64.msix")); - - auto task = mgr.UpdatePackageAsync(uri, deps, DeploymentOptions::ForceApplicationShutdown); - auto result = task.get(); - throw "Not implemented"; } } diff --git a/tf2_bot_detector_common/CMakeLists.txt b/tf2_bot_detector_common/CMakeLists.txt new file mode 100644 index 00000000..09b4fee3 --- /dev/null +++ b/tf2_bot_detector_common/CMakeLists.txt @@ -0,0 +1,8 @@ + +add_library(tf2_bot_detector_common INTERFACE) + +target_sources(tf2_bot_detector_common INTERFACE + "include/ReleaseChannel.h" +) + +target_include_directories(tf2_bot_detector_common INTERFACE include) diff --git a/tf2_bot_detector/ReleaseChannel.h b/tf2_bot_detector_common/include/ReleaseChannel.h similarity index 100% rename from tf2_bot_detector/ReleaseChannel.h rename to tf2_bot_detector_common/include/ReleaseChannel.h diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt new file mode 100644 index 00000000..1765c810 --- /dev/null +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -0,0 +1,23 @@ +project(tf2_bot_detector_updater) + +add_executable(tf2_bot_detector_updater + "main.cpp" + "Common.h" +) + +if (WIN32) + target_sources(tf2_bot_detector_updater PRIVATE + "Update_MSIX.cpp" + "Update_MSIX.h" + ) +endif() + +set_property(TARGET tf2_bot_detector_updater PROPERTY CXX_STANDARD 17) + +find_package(fmt CONFIG REQUIRED) + +target_link_libraries(tf2_bot_detector_updater PRIVATE + tf2_bot_detector_common + mh_stuff + fmt::fmt +) diff --git a/tf2_bot_detector_updater/Common.h b/tf2_bot_detector_updater/Common.h new file mode 100644 index 00000000..6dcc89c1 --- /dev/null +++ b/tf2_bot_detector_updater/Common.h @@ -0,0 +1,42 @@ +#pragma once + +#include "ReleaseChannel.h" + +#include + +#include +#include +#include +#include + +namespace tf2_bot_detector::Updater +{ + enum class UpdateType + { + Unknown = 0, + +#ifdef _WIN32 + MSIX, + // MSI -- maybe one day... +#endif + + //Portable, + }; + + struct CmdLineArgs + { + UpdateType m_UpdateType = UpdateType::Unknown; + ReleaseChannel m_ReleaseChannel = ReleaseChannel::None; + }; + + extern CmdLineArgs s_CmdLineArgs; +} + +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::Updater::UpdateType) + MH_ENUM_REFLECT_VALUE(Unknown) + +#ifdef _WIN32 + MH_ENUM_REFLECT_VALUE(MSIX) +#endif + +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector_updater/Update_MSIX.cpp b/tf2_bot_detector_updater/Update_MSIX.cpp new file mode 100644 index 00000000..b5b2134c --- /dev/null +++ b/tf2_bot_detector_updater/Update_MSIX.cpp @@ -0,0 +1,82 @@ +#include "Common.h" +#include "Update_MSIX.h" + +#include +#include + +#include + +#include +#undef max +#undef min + +#include +#include +#include +#include + +#pragma comment(lib, "windowsapp") + +using namespace tf2_bot_detector::Updater; +using namespace winrt::Windows::ApplicationModel; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Management::Deployment; + +int tf2_bot_detector::Updater::Update_MSIX() try +{ + std::cerr << "Attempting to install via API..." << std::endl; + + const auto mainBundleURL = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", + s_CmdLineArgs.m_ReleaseChannel); + + std::wcerr << "Main bundle URL: " << mainBundleURL; + const Uri uri = mainBundleURL; + + IVector deps{ winrt::single_threaded_vector() }; + + unsigned bits = 86; + { + SYSTEM_INFO sysInfo; + GetNativeSystemInfo(&sysInfo); + if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) + bits = 64; + } + + const auto depURL = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/vcredist.x{}.msix", bits); + std::wcerr << "Dependency URL: " << depURL; + deps.Append(Uri(depURL)); + + PackageManager mgr; + + std::cerr << "Starting async update..." << std::endl; + auto task = mgr.AddPackageAsync(uri, deps, DeploymentOptions::ForceApplicationShutdown); + + std::cerr << "Waiting for results..." << std::endl; + auto waitStatus = task.wait_for(TimeSpan::max()); + if (waitStatus != AsyncStatus::Completed) + { + std::wcerr << "Error encountered during async update: " + << task.ErrorCode() << ": " << task.get().ErrorText().c_str(); + return false; + } + else if (waitStatus == AsyncStatus::Completed) + { + std::cerr << "Update complete" << std::endl; + } + else + { + std::cerr << "Unknown update result" << std::endl; + } + + std::cerr << "Reopening tool..." << std::endl; + ShellExecuteA(nullptr, "open", "tf2bd:", nullptr, nullptr, SW_SHOWNORMAL); + + return 0; +} +catch (const std::exception& e) +{ + std::cerr << mh::format("Unhandled exception ({}) in {}: {}", + typeid(e).name(), __FUNCTION__, e.what()) << std::endl; + return 2; +} diff --git a/tf2_bot_detector_updater/Update_MSIX.h b/tf2_bot_detector_updater/Update_MSIX.h new file mode 100644 index 00000000..ae70ba12 --- /dev/null +++ b/tf2_bot_detector_updater/Update_MSIX.h @@ -0,0 +1,6 @@ +#pragma once + +namespace tf2_bot_detector::Updater +{ + int Update_MSIX(); +} diff --git a/tf2_bot_detector_updater/main.cpp b/tf2_bot_detector_updater/main.cpp new file mode 100644 index 00000000..8cdf0f01 --- /dev/null +++ b/tf2_bot_detector_updater/main.cpp @@ -0,0 +1,91 @@ +#include "Common.h" +#include "ReleaseChannel.h" + +#include + +#include +#include + +#ifdef _WIN32 +#include "Update_MSIX.h" +#endif + +using namespace tf2_bot_detector; +using namespace tf2_bot_detector::Updater; + +CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; + +static int MainHelper(int argc, char** argv) try +{ + for (int i = 1; i < argc; i++) + { + const bool hasNextArg = i < (argc - 1); + const std::string_view arg(argv[i]); + if (arg == "--update-type") + { + if (!hasNextArg) + { + std::cerr << "Missing argument for " << arg << std::endl; + return 1; + } + + s_CmdLineArgs.m_UpdateType = mh::enum_type::find_value(argv[i + 1]); + } + else if (arg == "--release-channel") + { + if (!hasNextArg) + { + std::cerr << "Missing argument for " << arg << std::endl; + return 1; + } + + s_CmdLineArgs.m_ReleaseChannel = mh::enum_type::find_value(argv[i + 1]); + } + } + + if (s_CmdLineArgs.m_ReleaseChannel == ReleaseChannel::None) + { + std::cerr << mh::format("Release channel not specified. Use --release-channel <{:v}|{:v}|{:v}>", + ReleaseChannel::Public, ReleaseChannel::Preview, ReleaseChannel::Nightly); + return 1; + } + + if (s_CmdLineArgs.m_UpdateType == UpdateType::Unknown) + { + std::cerr << "Update type not specified. Use --update-type with one of the following values:\n"; + + for (const auto& value : mh::enum_type::VALUES) + { + if (value.value() == UpdateType::Unknown) + continue; + + std::cerr << '\t' << value.value_name() << '\n'; + } + + std::cerr << std::flush; + return 1; + } + + switch (s_CmdLineArgs.m_UpdateType) + { +#ifdef _WIN32 + case UpdateType::MSIX: + return Update_MSIX(); +#endif + } + + return 0; +} +catch (const std::exception& e) +{ + std::cerr << "Unhandled exception: " << typeid(e).name() << ": " << e.what(); + return 1; +} + +int main(int argc, char** argv) +{ + std::cerr << std::endl; + MainHelper(argc, argv); + std::cerr << std::endl << std::endl; +} + From feb0df172e6737fd18334a2501438e5f5f8119b0 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 02:01:06 -0700 Subject: [PATCH 085/161] Fixed a missing newline --- tf2_bot_detector_updater/Update_MSIX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tf2_bot_detector_updater/Update_MSIX.cpp b/tf2_bot_detector_updater/Update_MSIX.cpp index b5b2134c..c6c8d396 100644 --- a/tf2_bot_detector_updater/Update_MSIX.cpp +++ b/tf2_bot_detector_updater/Update_MSIX.cpp @@ -44,7 +44,7 @@ int tf2_bot_detector::Updater::Update_MSIX() try } const auto depURL = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/vcredist.x{}.msix", bits); - std::wcerr << "Dependency URL: " << depURL; + std::wcerr << "Dependency URL: " << depURL << std::endl; deps.Append(Uri(depURL)); PackageManager mgr; From 15a320efc3bf421b279a3b61b0be13a4535a38d6 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 12:18:15 -0700 Subject: [PATCH 086/161] Split up the giant root CMakeLists.txt --- CMakeLists.txt | 238 +------------------------------ tf2_bot_detector/CMakeLists.txt | 239 ++++++++++++++++++++++++++++++++ 2 files changed, 241 insertions(+), 236 deletions(-) create mode 100644 tf2_bot_detector/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index fe7efdfa..082fe7b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,6 @@ cmake_minimum_required(VERSION 3.17.2) project(tf2_bot_detector VERSION 1.1.0) -include(GenerateExportHeader) - message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") # We use this as the build number. @@ -20,7 +18,7 @@ endif() # Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! if (MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /cgthreads1") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") @@ -37,255 +35,23 @@ add_subdirectory(submodules/imgui_desktop) add_subdirectory(submodules/mh_stuff) add_subdirectory(tf2_bot_detector_common) add_subdirectory(tf2_bot_detector_updater) +add_subdirectory(tf2_bot_detector) set(ENABLE_EXAMPLES off CACHE BOOL "Build examples" FORCE) add_subdirectory("submodules/cppcoro") -if (WIN32) - add_library(tf2_bot_detector SHARED) - set_target_properties(tf2_bot_detector PROPERTIES PDB_NAME "tf2_bot_detector_dll") - - generate_export_header(tf2_bot_detector - EXPORT_FILE_NAME "tf2_bot_detector/tf2_bot_detector_export.h" - ) - - add_executable(tf2_bot_detector_launcher WIN32 - "tf2_bot_detector/Platform/Windows/CrashHandler.cpp" - "tf2_bot_detector/Launcher/main.cpp" - "tf2_bot_detector/Launcher/Resources.rc" - ) - target_compile_definitions(tf2_bot_detector_launcher PRIVATE TF2BD_LAUNCHER_USE_WINMAIN) - - target_include_directories(tf2_bot_detector_launcher PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/tf2_bot_detector) - target_link_libraries(tf2_bot_detector_launcher PRIVATE tf2_bot_detector) - set_target_properties(tf2_bot_detector_launcher PROPERTIES OUTPUT_NAME "tf2_bot_detector") - set_property(TARGET tf2_bot_detector_launcher PROPERTY CXX_STANDARD 17) -else() - add_executable(tf2_bot_detector - "tf2_bot_detector/Launcher/main.cpp" - ) -endif() - -target_sources(tf2_bot_detector PRIVATE - "tf2_bot_detector/Actions/RCONActionManager.cpp" - "tf2_bot_detector/Actions/RCONActionManager.h" - "tf2_bot_detector/Actions/ActionGenerators.cpp" - "tf2_bot_detector/Actions/ActionGenerators.h" - "tf2_bot_detector/Actions/Actions.cpp" - "tf2_bot_detector/Actions/Actions.h" - "tf2_bot_detector/Actions/HijackActionManager.h" - "tf2_bot_detector/Actions/HijackActionManager.cpp" - "tf2_bot_detector/Actions/IActionManager.h" - "tf2_bot_detector/Actions/ICommandSource.h" - "tf2_bot_detector/Config/ConfigHelpers.cpp" - "tf2_bot_detector/Config/ConfigHelpers.h" - "tf2_bot_detector/Config/DRPInfo.cpp" - "tf2_bot_detector/Config/DRPInfo.h" - "tf2_bot_detector/Config/PlayerListJSON.cpp" - "tf2_bot_detector/Config/PlayerListJSON.h" - "tf2_bot_detector/Config/Rules.cpp" - "tf2_bot_detector/Config/Rules.h" - "tf2_bot_detector/Config/Settings.cpp" - "tf2_bot_detector/Config/Settings.h" - "tf2_bot_detector/Config/SponsorsList.h" - "tf2_bot_detector/Config/SponsorsList.cpp" - "tf2_bot_detector/ConsoleLog/ConsoleLogParser.h" - "tf2_bot_detector/ConsoleLog/ConsoleLogParser.cpp" - "tf2_bot_detector/ConsoleLog/ConsoleLines.cpp" - "tf2_bot_detector/ConsoleLog/ConsoleLines.h" - "tf2_bot_detector/ConsoleLog/IConsoleLine.h" - "tf2_bot_detector/ConsoleLog/ConsoleLineListener.cpp" - "tf2_bot_detector/ConsoleLog/ConsoleLineListener.h" - "tf2_bot_detector/ConsoleLog/NetworkStatus.cpp" - "tf2_bot_detector/ConsoleLog/NetworkStatus.h" - "tf2_bot_detector/GameData/MatchmakingQueue.h" - "tf2_bot_detector/GameData/TFClassType.h" - "tf2_bot_detector/GameData/TFParty.h" - "tf2_bot_detector/GameData/UserMessageType.h" - "tf2_bot_detector/Networking/GithubAPI.h" - "tf2_bot_detector/Networking/GithubAPI.cpp" - "tf2_bot_detector/Networking/HTTPClient.h" - "tf2_bot_detector/Networking/HTTPClient.cpp" - "tf2_bot_detector/Networking/HTTPHelpers.h" - "tf2_bot_detector/Networking/HTTPHelpers.cpp" - "tf2_bot_detector/Networking/NetworkHelpers.h" - "tf2_bot_detector/Networking/NetworkHelpers.cpp" - "tf2_bot_detector/Networking/SteamAPI.h" - "tf2_bot_detector/Networking/SteamAPI.cpp" - "tf2_bot_detector/Platform/Platform.h" - "tf2_bot_detector/SetupFlow/BasicSettingsPage.h" - "tf2_bot_detector/SetupFlow/BasicSettingsPage.cpp" - "tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.h" - "tf2_bot_detector/SetupFlow/ChatWrappersGeneratorPage.cpp" - "tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.h" - "tf2_bot_detector/SetupFlow/ChatWrappersVerifyPage.cpp" - "tf2_bot_detector/SetupFlow/ISetupFlowPage.h" - "tf2_bot_detector/SetupFlow/NetworkSettingsPage.h" - "tf2_bot_detector/SetupFlow/NetworkSettingsPage.cpp" - "tf2_bot_detector/SetupFlow/SetupFlow.cpp" - "tf2_bot_detector/SetupFlow/SetupFlow.h" - "tf2_bot_detector/SetupFlow/TF2CommandLinePage.h" - "tf2_bot_detector/SetupFlow/TF2CommandLinePage.cpp" - "tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp" - "tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp" - "tf2_bot_detector/UI/ImGui_TF2BotDetector.h" - "tf2_bot_detector/UI/MainWindow.cpp" - "tf2_bot_detector/UI/MainWindow.Scoreboard.cpp" - "tf2_bot_detector/UI/MainWindow.h" - "tf2_bot_detector/Util/JSONUtils.h" - "tf2_bot_detector/Util/PathUtils.cpp" - "tf2_bot_detector/Util/PathUtils.h" - "tf2_bot_detector/Util/TextUtils.cpp" - "tf2_bot_detector/Util/TextUtils.h" - "tf2_bot_detector/BaseTextures.h" - "tf2_bot_detector/BaseTextures.cpp" - "tf2_bot_detector/BatchedAction.h" - "tf2_bot_detector/Bitmap.h" - "tf2_bot_detector/Bitmap.cpp" - "tf2_bot_detector/Clock.cpp" - "tf2_bot_detector/Clock.h" - "tf2_bot_detector/CompensatedTS.cpp" - "tf2_bot_detector/CompensatedTS.h" - "tf2_bot_detector/Config/ChatWrappers.cpp" - "tf2_bot_detector/Config/ChatWrappers.h" - "tf2_bot_detector/DLLMain.cpp" - "tf2_bot_detector/DLLMain.h" - "tf2_bot_detector/Filesystem.cpp" - "tf2_bot_detector/Filesystem.h" - "tf2_bot_detector/IPlayer.cpp" - "tf2_bot_detector/IPlayer.h" - "tf2_bot_detector/Log.cpp" - "tf2_bot_detector/Log.h" - "tf2_bot_detector/ModeratorLogic.cpp" - "tf2_bot_detector/ModeratorLogic.h" - "tf2_bot_detector/PlayerStatus.h" - "tf2_bot_detector/SteamID.cpp" - "tf2_bot_detector/SteamID.h" - "tf2_bot_detector/TextureManager.h" - "tf2_bot_detector/TextureManager.cpp" - "tf2_bot_detector/TFConstants.h" - "tf2_bot_detector/UpdateManager.h" - "tf2_bot_detector/UpdateManager.cpp" - "tf2_bot_detector/Version.h" - "tf2_bot_detector/Version.cpp" - "tf2_bot_detector/WorldEventListener.cpp" - "tf2_bot_detector/WorldEventListener.h" - "tf2_bot_detector/WorldState.cpp" - "tf2_bot_detector/WorldState.h" -) - -configure_file(tf2_bot_detector/Version.base.h tf2_bot_detector/Version.h) -target_include_directories(tf2_bot_detector PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/tf2_bot_detector) - -if(WIN32) - if ((CMAKE_BUILD_TYPE MATCHES "Release")) - set(TF2BD_RESOURCE_FILEFLAGS "0") - else() - set(TF2BD_RESOURCE_FILEFLAGS "VS_FF_DEBUG") - endif() - configure_file(tf2_bot_detector/Resources.base.rc tf2_bot_detector/Resources.rc) - - target_sources(tf2_bot_detector PRIVATE - "tf2_bot_detector/Platform/Windows/Processes.cpp" - "tf2_bot_detector/Platform/Windows/Shell.cpp" - "tf2_bot_detector/Platform/Windows/Steam.cpp" - "tf2_bot_detector/Platform/Windows/WindowsHelpers.h" - "tf2_bot_detector/Resources.rc" - "tf2_bot_detector/Platform/Windows/Windows.cpp" - "tf2_bot_detector/Platform/Windows/PlatformInstall.cpp" - ) -endif() - -target_include_directories(tf2_bot_detector - PRIVATE tf2_bot_detector -) -target_compile_definitions(tf2_bot_detector PRIVATE WIN32_LEAN_AND_MEAN) - option(TF2BD_ENABLE_DISCORD_INTEGRATION "Enable discord integration" on) -if (TF2BD_ENABLE_DISCORD_INTEGRATION) - target_compile_definitions(tf2_bot_detector PRIVATE TF2BD_ENABLE_DISCORD_INTEGRATION) - find_library(DISCORD_GAME_SDK discord_game_sdk) - message("DISCORD_GAME_SDK = ${DISCORD_GAME_SDK}") - target_link_libraries(tf2_bot_detector PRIVATE ${DISCORD_GAME_SDK}) - - find_path(GAME_SDK_INCLUDE discord-game-sdk/discord.h) - target_include_directories(tf2_bot_detector PRIVATE ${DISCORD_GAME_SDK_INCLUDE}) - - find_library(DISCORD_CPP_GAME_SDK discord_game_sdk_cpp) - message("DISCORD_CPP_GAME_SDK = ${DISCORD_CPP_GAME_SDK}") - target_link_libraries(tf2_bot_detector PRIVATE ${DISCORD_CPP_GAME_SDK}) - - target_sources(tf2_bot_detector PRIVATE - "tf2_bot_detector/DiscordRichPresence.cpp" - "tf2_bot_detector/DiscordRichPresence.h" - ) -endif() - -find_package(OpenSSL REQUIRED) -target_link_libraries(tf2_bot_detector) - -find_path(HTTPLIB_PATH NAMES httplib.h) -target_include_directories(tf2_bot_detector PRIVATE ${HTTPLIB_PATH}) - -find_package(nlohmann_json CONFIG REQUIRED) -find_package(libzippp CONFIG REQUIRED) -find_package(fmt CONFIG REQUIRED) - -target_link_libraries(tf2_bot_detector PRIVATE - tf2_bot_detector_common - imgui_desktop - mh_stuff - ValveFileVDF - libzip::libzip - libzippp::libzippp - cppcoro - SourceRCON - OpenSSL::SSL # cpp-httplib requires openssl - nlohmann_json::nlohmann_json - fmt::fmt -) - option(TF2BD_ENABLE_TESTS "Enable test compilation" off) -if (TF2BD_ENABLE_TESTS) - enable_testing() - - find_package(Catch2 CONFIG REQUIRED) - target_link_libraries(tf2_bot_detector PRIVATE Catch2::Catch2) - target_compile_definitions(tf2_bot_detector PRIVATE TF2BD_ENABLE_TESTS) - target_sources(tf2_bot_detector PRIVATE - "tf2_bot_detector/Tests/Catch2.cpp" - "tf2_bot_detector/Tests/ConsoleLineTests.cpp" - "tf2_bot_detector/Tests/Tests.h" - ) - - SET(TF2BD_ENABLE_CLI_EXE true) - - add_test(NAME TF2BD_Tests COMMAND tf2_bot_detector_cli --run-tests - WORKING_DIRECTORY staging - ) -endif() - -if(TF2BD_ENABLE_CLI_EXE) - add_executable(tf2_bot_detector_cli "tf2_bot_detector/Launcher/main.cpp") - target_include_directories(tf2_bot_detector_cli PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/tf2_bot_detector) - target_link_libraries(tf2_bot_detector_cli PRIVATE tf2_bot_detector) - set_property(TARGET tf2_bot_detector_cli PROPERTY CXX_STANDARD 17) -endif() # TODO: Find a way to do this locally if(MSVC) target_compile_options(tf2_bot_detector PRIVATE /WX) endif() -set_property(TARGET tf2_bot_detector PROPERTY CXX_STANDARD 20) if (MSVC) add_definitions(/await) endif() -set_target_properties(tf2_bot_detector PROPERTIES - VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/staging" -) - # "installation" aka create a build we can upload to github as a release if (WIN32) file(GLOB TF2BD_INSTALL_DEPS_DLL LIST_DIRECTORIES false "${CMAKE_BINARY_DIR}/*.dll") diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt new file mode 100644 index 00000000..514bbca4 --- /dev/null +++ b/tf2_bot_detector/CMakeLists.txt @@ -0,0 +1,239 @@ +include(GenerateExportHeader) + +if (WIN32) + add_library(tf2_bot_detector SHARED) + set_target_properties(tf2_bot_detector PROPERTIES PDB_NAME "tf2_bot_detector_dll") + + generate_export_header(tf2_bot_detector + EXPORT_FILE_NAME "tf2_bot_detector_export.h" + ) + + add_executable(tf2_bot_detector_launcher WIN32 + "Platform/Windows/CrashHandler.cpp" + "Launcher/main.cpp" + "Launcher/Resources.rc" + ) + target_compile_definitions(tf2_bot_detector_launcher PRIVATE TF2BD_LAUNCHER_USE_WINMAIN) + + target_include_directories(tf2_bot_detector_launcher PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(tf2_bot_detector_launcher PRIVATE tf2_bot_detector) + set_target_properties(tf2_bot_detector_launcher PROPERTIES OUTPUT_NAME "tf2_bot_detector") + set_property(TARGET tf2_bot_detector_launcher PROPERTY CXX_STANDARD 17) +else() + add_executable(tf2_bot_detector + "Launcher/main.cpp" + ) +endif() + +set_target_properties(tf2_bot_detector PROPERTIES + VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/staging" + CXX_STANDARD 20 +) + +target_compile_definitions(tf2_bot_detector PRIVATE WIN32_LEAN_AND_MEAN) +target_include_directories(tf2_bot_detector PRIVATE + "." + "${CMAKE_CURRENT_BINARY_DIR}" +) + +configure_file(Version.base.h Version.h) + +message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}") +get_target_property(dirs tf2_bot_detector INCLUDE_DIRECTORIES) +foreach(dir ${dirs}) + message(STATUS "dir='${dir}'") +endforeach() + +target_sources(tf2_bot_detector PRIVATE + "Actions/RCONActionManager.cpp" + "Actions/RCONActionManager.h" + "Actions/ActionGenerators.cpp" + "Actions/ActionGenerators.h" + "Actions/Actions.cpp" + "Actions/Actions.h" + "Actions/HijackActionManager.h" + "Actions/HijackActionManager.cpp" + "Actions/IActionManager.h" + "Actions/ICommandSource.h" + "Config/ConfigHelpers.cpp" + "Config/ConfigHelpers.h" + "Config/DRPInfo.cpp" + "Config/DRPInfo.h" + "Config/PlayerListJSON.cpp" + "Config/PlayerListJSON.h" + "Config/Rules.cpp" + "Config/Rules.h" + "Config/Settings.cpp" + "Config/Settings.h" + "Config/SponsorsList.h" + "Config/SponsorsList.cpp" + "ConsoleLog/ConsoleLogParser.h" + "ConsoleLog/ConsoleLogParser.cpp" + "ConsoleLog/ConsoleLines.cpp" + "ConsoleLog/ConsoleLines.h" + "ConsoleLog/IConsoleLine.h" + "ConsoleLog/ConsoleLineListener.cpp" + "ConsoleLog/ConsoleLineListener.h" + "ConsoleLog/NetworkStatus.cpp" + "ConsoleLog/NetworkStatus.h" + "GameData/MatchmakingQueue.h" + "GameData/TFClassType.h" + "GameData/TFParty.h" + "GameData/UserMessageType.h" + "Networking/GithubAPI.h" + "Networking/GithubAPI.cpp" + "Networking/HTTPClient.h" + "Networking/HTTPClient.cpp" + "Networking/HTTPHelpers.h" + "Networking/HTTPHelpers.cpp" + "Networking/NetworkHelpers.h" + "Networking/NetworkHelpers.cpp" + "Networking/SteamAPI.h" + "Networking/SteamAPI.cpp" + "Platform/Platform.h" + "SetupFlow/BasicSettingsPage.h" + "SetupFlow/BasicSettingsPage.cpp" + "SetupFlow/ChatWrappersGeneratorPage.h" + "SetupFlow/ChatWrappersGeneratorPage.cpp" + "SetupFlow/ChatWrappersVerifyPage.h" + "SetupFlow/ChatWrappersVerifyPage.cpp" + "SetupFlow/ISetupFlowPage.h" + "SetupFlow/NetworkSettingsPage.h" + "SetupFlow/NetworkSettingsPage.cpp" + "SetupFlow/SetupFlow.cpp" + "SetupFlow/SetupFlow.h" + "SetupFlow/TF2CommandLinePage.h" + "SetupFlow/TF2CommandLinePage.cpp" + "SetupFlow/UpdateCheckPage.cpp" + "UI/ImGui_TF2BotDetector.cpp" + "UI/ImGui_TF2BotDetector.h" + "UI/MainWindow.cpp" + "UI/MainWindow.Scoreboard.cpp" + "UI/MainWindow.h" + "Util/JSONUtils.h" + "Util/PathUtils.cpp" + "Util/PathUtils.h" + "Util/TextUtils.cpp" + "Util/TextUtils.h" + "BaseTextures.h" + "BaseTextures.cpp" + "BatchedAction.h" + "Bitmap.h" + "Bitmap.cpp" + "Clock.cpp" + "Clock.h" + "CompensatedTS.cpp" + "CompensatedTS.h" + "Config/ChatWrappers.cpp" + "Config/ChatWrappers.h" + "DLLMain.cpp" + "DLLMain.h" + "Filesystem.cpp" + "Filesystem.h" + "IPlayer.cpp" + "IPlayer.h" + "Log.cpp" + "Log.h" + "ModeratorLogic.cpp" + "ModeratorLogic.h" + "PlayerStatus.h" + "SteamID.cpp" + "SteamID.h" + "TextureManager.h" + "TextureManager.cpp" + "TFConstants.h" + "UpdateManager.h" + "UpdateManager.cpp" + "Version.h" + "Version.cpp" + "WorldEventListener.cpp" + "WorldEventListener.h" + "WorldState.cpp" + "WorldState.h" +) + +if(WIN32) + if ((CMAKE_BUILD_TYPE MATCHES "Release")) + set(TF2BD_RESOURCE_FILEFLAGS "0") + else() + set(TF2BD_RESOURCE_FILEFLAGS "VS_FF_DEBUG") + endif() + configure_file(Resources.base.rc Resources.rc) + + target_sources(tf2_bot_detector PRIVATE + "Platform/Windows/Processes.cpp" + "Platform/Windows/Shell.cpp" + "Platform/Windows/Steam.cpp" + "Platform/Windows/WindowsHelpers.h" + "Resources.rc" + "Platform/Windows/Windows.cpp" + "Platform/Windows/PlatformInstall.cpp" + ) +endif() + +if (TF2BD_ENABLE_DISCORD_INTEGRATION) + target_compile_definitions(tf2_bot_detector PRIVATE TF2BD_ENABLE_DISCORD_INTEGRATION) + find_library(DISCORD_GAME_SDK discord_game_sdk) + message("DISCORD_GAME_SDK = ${DISCORD_GAME_SDK}") + target_link_libraries(tf2_bot_detector PRIVATE ${DISCORD_GAME_SDK}) + + find_path(GAME_SDK_INCLUDE discord-game-sdk/discord.h) + target_include_directories(tf2_bot_detector PRIVATE ${DISCORD_GAME_SDK_INCLUDE}) + + find_library(DISCORD_CPP_GAME_SDK discord_game_sdk_cpp) + message("DISCORD_CPP_GAME_SDK = ${DISCORD_CPP_GAME_SDK}") + target_link_libraries(tf2_bot_detector PRIVATE ${DISCORD_CPP_GAME_SDK}) + + target_sources(tf2_bot_detector PRIVATE + "tf2_bot_detector/DiscordRichPresence.cpp" + "tf2_bot_detector/DiscordRichPresence.h" + ) +endif() + +find_package(OpenSSL REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) +find_package(libzippp CONFIG REQUIRED) +find_package(fmt CONFIG REQUIRED) + +target_link_libraries(tf2_bot_detector PRIVATE + tf2_bot_detector_common + imgui_desktop + mh_stuff + ValveFileVDF + libzip::libzip + libzippp::libzippp + cppcoro + SourceRCON + OpenSSL::SSL # cpp-httplib requires openssl + nlohmann_json::nlohmann_json + fmt::fmt +) + +find_path(HTTPLIB_PATH NAMES httplib.h) +target_include_directories(tf2_bot_detector PRIVATE ${HTTPLIB_PATH}) + +if (TF2BD_ENABLE_TESTS) + enable_testing() + + find_package(Catch2 CONFIG REQUIRED) + target_link_libraries(tf2_bot_detector PRIVATE Catch2::Catch2) + target_compile_definitions(tf2_bot_detector PRIVATE TF2BD_ENABLE_TESTS) + target_sources(tf2_bot_detector PRIVATE + "Tests/Catch2.cpp" + "Tests/ConsoleLineTests.cpp" + "Tests/Tests.h" + ) + + SET(TF2BD_ENABLE_CLI_EXE true) + + add_test(NAME TF2BD_Tests COMMAND tf2_bot_detector_cli --run-tests + WORKING_DIRECTORY staging + ) +endif() + +if(TF2BD_ENABLE_CLI_EXE) + add_executable(tf2_bot_detector_cli "Launcher/main.cpp") + target_include_directories(tf2_bot_detector_cli PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(tf2_bot_detector_cli PRIVATE tf2_bot_detector) + set_property(TARGET tf2_bot_detector_cli PROPERTY CXX_STANDARD 17) +endif() From 427200f9e8fc49d6f760c80c050fc03c82f29d96 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 13:11:23 -0700 Subject: [PATCH 087/161] Fixed compile error. --- tf2_bot_detector/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index 514bbca4..fd0143e9 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -185,8 +185,8 @@ if (TF2BD_ENABLE_DISCORD_INTEGRATION) target_link_libraries(tf2_bot_detector PRIVATE ${DISCORD_CPP_GAME_SDK}) target_sources(tf2_bot_detector PRIVATE - "tf2_bot_detector/DiscordRichPresence.cpp" - "tf2_bot_detector/DiscordRichPresence.h" + "DiscordRichPresence.cpp" + "DiscordRichPresence.h" ) endif() From f56a2ba6ff0ef5ab30cfc7fc8eb71733e602343d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 13:17:25 -0700 Subject: [PATCH 088/161] Improved compile times with precompiled headers, based on info from vcperf. --- CMakeLists.txt | 1 - tf2_bot_detector/CMakeLists.txt | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 082fe7b3..c2acc305 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,6 @@ endif() # Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! if (MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /cgthreads1") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index fd0143e9..1692b9d1 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 3.16) + include(GenerateExportHeader) if (WIN32) @@ -38,12 +40,6 @@ target_include_directories(tf2_bot_detector PRIVATE configure_file(Version.base.h Version.h) -message("CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}") -get_target_property(dirs tf2_bot_detector INCLUDE_DIRECTORIES) -foreach(dir ${dirs}) - message(STATUS "dir='${dir}'") -endforeach() - target_sources(tf2_bot_detector PRIVATE "Actions/RCONActionManager.cpp" "Actions/RCONActionManager.h" @@ -152,6 +148,13 @@ target_sources(tf2_bot_detector PRIVATE "WorldState.h" ) +target_precompile_headers(tf2_bot_detector + PUBLIC + "Clock.h" + PRIVATE + "Log.h" +) + if(WIN32) if ((CMAKE_BUILD_TYPE MATCHES "Release")) set(TF2BD_RESOURCE_FILEFLAGS "0") From 6ae284478e0237e4c99bd5e41113adf7863de5b6 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 13:32:09 -0700 Subject: [PATCH 089/161] Force tf2_bot_detector_updater into a subdirectory. --- tf2_bot_detector_updater/CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index 1765c810..a3399bbf 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -12,7 +12,12 @@ if (WIN32) ) endif() -set_property(TARGET tf2_bot_detector_updater PROPERTY CXX_STANDARD 17) +set_target_properties(tf2_bot_detector_updater PROPERTIES + CXX_STANDARD 17 + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" +) find_package(fmt CONFIG REQUIRED) From 89047b7a6df993eae49ab671ecd67f0043fd2551 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 14:07:50 -0700 Subject: [PATCH 090/161] Create an artifact for the updater --- .github/workflows/ccpp.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 739f62a3..b2df14da 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -131,6 +131,14 @@ jobs: certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + - name: "Artifacts: tf2_bot_detector_updater" + uses: actions/upload-artifact@v2 + with: + name: "updater_${{ matrix.triplet }}_${{ matrix.build_type }}" + path: | + ${{ steps.tf2bd_paths.outputs.build_dir }}/tf2_bot_detector_updater/*.dll + ${{ steps.tf2bd_paths.outputs.build_dir }}/tf2_bot_detector_updater/*.exe + - name: "Artifacts: Fresh, signed exe" if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 From 9259f12e12f01e4ce453812f818638e4da6d3ceb Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 14:08:58 -0700 Subject: [PATCH 091/161] Conditionally upload artifacts --- .github/workflows/ccpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index b2df14da..7929a760 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -132,6 +132,7 @@ jobs: password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - name: "Artifacts: tf2_bot_detector_updater" + if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD uses: actions/upload-artifact@v2 with: name: "updater_${{ matrix.triplet }}_${{ matrix.build_type }}" From f43c6093a505ef6835cad5e13559a4f02782a777 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 17:31:26 -0700 Subject: [PATCH 092/161] * Statically link CRT to tf2_bot_detector_updater * Use header-only version of fmt --- submodules/mh_stuff | 2 +- tf2_bot_detector_updater/CMakeLists.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index f3c5c88e..9b202aef 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit f3c5c88ef489745839106eea9c39f7ebb1759c2b +Subproject commit 9b202aeff4862a989fd5bd6a23f6b19ed7ce370c diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index a3399bbf..d1e03646 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -17,6 +17,7 @@ set_target_properties(tf2_bot_detector_updater PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ) find_package(fmt CONFIG REQUIRED) @@ -24,5 +25,5 @@ find_package(fmt CONFIG REQUIRED) target_link_libraries(tf2_bot_detector_updater PRIVATE tf2_bot_detector_common mh_stuff - fmt::fmt + fmt::fmt-header-only ) From 6def72f53a3a39042c9cf6e3b3e8722c4eaa0515 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 29 Aug 2020 21:24:21 -0700 Subject: [PATCH 093/161] Consistent artifact names --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7929a760..f5077463 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -135,7 +135,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD uses: actions/upload-artifact@v2 with: - name: "updater_${{ matrix.triplet }}_${{ matrix.build_type }}" + name: "updater_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" path: | ${{ steps.tf2bd_paths.outputs.build_dir }}/tf2_bot_detector_updater/*.dll ${{ steps.tf2bd_paths.outputs.build_dir }}/tf2_bot_detector_updater/*.exe @@ -144,7 +144,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: - name: "smartscreen_${{ matrix.triplet }}_${{ matrix.build_type }}" + name: "smartscreen_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" @@ -176,7 +176,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" - path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.pdb" + path: "${{ steps.tf2bd_paths.outputs.build_dir }}/**.pdb" From 7e5158a32faa590c53ac179ae1e3703b56413b76 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 30 Aug 2020 18:51:14 -0700 Subject: [PATCH 094/161] Initial version of newer-er-est updater code --- CMakeLists.txt | 20 +- submodules/mh_stuff | 2 +- tf2_bot_detector/CMakeLists.txt | 4 +- tf2_bot_detector/Config/Settings.cpp | 19 +- tf2_bot_detector/Log.cpp | 3 +- tf2_bot_detector/Log.h | 33 +- tf2_bot_detector/Networking/HTTPClient.cpp | 4 +- tf2_bot_detector/Platform/Platform.h | 54 +- .../Platform/Windows/Platform.cpp | 35 ++ .../Platform/Windows/PlatformInstall.cpp | 51 +- .../SetupFlow/UpdateCheckPage.cpp | 31 +- tf2_bot_detector/UpdateManager.cpp | 499 +++++++++++------- tf2_bot_detector/UpdateManager.h | 40 +- tf2_bot_detector/Util/JSONUtils.h | 4 +- tf2_bot_detector/Version.base.h | 5 + tf2_bot_detector/Version.cpp | 12 + 16 files changed, 543 insertions(+), 273 deletions(-) create mode 100644 tf2_bot_detector/Platform/Windows/Platform.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c2acc305..9af23680 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,16 +16,22 @@ else() add_compile_definitions(TF2BD_IS_CI_COMPILE=0) endif() -# Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! +set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE true) + if (MSVC) + string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob1") + +# Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") -endif() -set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE true) -if (MSVC AND (CMAKE_BUILD_TYPE MATCHES "Release")) - add_link_options(/OPT:REF /OPT:ICF) + if (CMAKE_BUILD_TYPE MATCHES "Release") + add_link_options(/OPT:REF /OPT:ICF) + endif() + + add_definitions(/await) endif() add_subdirectory(submodules/ValveFileVDF) @@ -47,10 +53,6 @@ if(MSVC) target_compile_options(tf2_bot_detector PRIVATE /WX) endif() -if (MSVC) - add_definitions(/await) -endif() - # "installation" aka create a build we can upload to github as a release if (WIN32) file(GLOB TF2BD_INSTALL_DEPS_DLL LIST_DIRECTORIES false "${CMAKE_BINARY_DIR}/*.dll") diff --git a/submodules/mh_stuff b/submodules/mh_stuff index 9b202aef..be2329dc 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit 9b202aeff4862a989fd5bd6a23f6b19ed7ce370c +Subproject commit be2329dc040a6e89087b4179816ed11226f41372 diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index 1692b9d1..701ef70f 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -146,7 +146,7 @@ target_sources(tf2_bot_detector PRIVATE "WorldEventListener.h" "WorldState.cpp" "WorldState.h" -) + ) target_precompile_headers(tf2_bot_detector PUBLIC @@ -171,7 +171,7 @@ if(WIN32) "Resources.rc" "Platform/Windows/Windows.cpp" "Platform/Windows/PlatformInstall.cpp" - ) + "Platform/Windows/Platform.cpp") endif() if (TF2BD_ENABLE_DISCORD_INTEGRATION) diff --git a/tf2_bot_detector/Config/Settings.cpp b/tf2_bot_detector/Config/Settings.cpp index 1ae335e4..0ef88847 100644 --- a/tf2_bot_detector/Config/Settings.cpp +++ b/tf2_bot_detector/Config/Settings.cpp @@ -147,28 +147,27 @@ void tf2_bot_detector::to_json(nlohmann::json& j, const ReleaseChannel& d) switch (d) { case ReleaseChannel::None: j = "disabled"; return; - case ReleaseChannel::Public: j = "releases"; return; - case ReleaseChannel::Preview: j = "previews"; return; + case ReleaseChannel::Public: j = "public"; return; + case ReleaseChannel::Preview: j = "preview"; return; case ReleaseChannel::Nightly: j = "nightly"; return; - - default: - throw std::invalid_argument("Unknown ReleaseChannel "s << +std::underlying_type_t(d)); } + + throw std::invalid_argument(mh::format("Unexpected value {}", mh::enum_fmt(d))); } void tf2_bot_detector::from_json(const nlohmann::json& j, ReleaseChannel& d) { - auto value = j.get(); - if (value == "releases"sv) + auto value = mh::tolower(j.get()); + if (value == "releases"sv || value == "public"sv) d = ReleaseChannel::Public; - else if (value == "previews"sv) + else if (value == "previews"sv || value == "preview"sv) d = ReleaseChannel::Preview; else if (value == "nightly"sv) d = ReleaseChannel::Nightly; - else if (value == "disabled"sv) + else if (value == "disabled"sv || value == "none"sv) d = ReleaseChannel::None; else - throw std::invalid_argument("Unknown ReleaseChannel "s << std::quoted(value)); + throw std::invalid_argument(mh::format("Unknown ReleaseChannel {}", std::quoted(value))); } Settings::Settings() diff --git a/tf2_bot_detector/Log.cpp b/tf2_bot_detector/Log.cpp index b232cee1..70795b39 100644 --- a/tf2_bot_detector/Log.cpp +++ b/tf2_bot_detector/Log.cpp @@ -25,6 +25,7 @@ using namespace std::chrono_literals; using namespace std::string_literals; +using namespace std::string_view_literals; using namespace tf2_bot_detector; namespace @@ -221,7 +222,7 @@ void LogManager::ReplaceSecrets(std::string& msg) const void tf2_bot_detector::logExFn(const mh::source_location& location, const std::exception& e, \ const std::string_view& msg) \ { \ - logFn(location, msg.empty() ? "{1}: {2}" : "{0}: {1}: {2}", msg, typeid(e).name(), e.what()); \ + logFn(location, msg.empty() ? "{1}: {2}"sv : "{0}: {1}: {2}"sv, msg, typeid(e).name(), e.what()); \ } LOG_EXCEPTION_IMPL(LogException, LogError); diff --git a/tf2_bot_detector/Log.h b/tf2_bot_detector/Log.h index 31ed62b6..cbb27d26 100644 --- a/tf2_bot_detector/Log.h +++ b/tf2_bot_detector/Log.h @@ -94,12 +94,32 @@ namespace tf2_bot_detector #define NOINLINE #endif + namespace detail::log_h + { + template 0)>> + NOINLINE inline void LogImpl(const LogMessageColor& color, LogSeverity severity, LogVisibility visibility, + const std::string_view& fmtStr, const TArgs&... args) + { + ILogManager::GetInstance().Log(mh::try_format(fmtStr, args...), color, severity, visibility); + } + template + NOINLINE inline void LogImpl(const LogMessageColor& color, LogSeverity severity, LogVisibility visibility, + const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) + { + using namespace std::string_view_literals; + if (!fmtStr.empty()) + LogImpl(color, severity, visibility, "{}: {}"sv, location, mh::try_format(fmtStr, args...)); + else + LogImpl(color, severity, visibility, "{}"sv, location); + } + } + #undef LOG_DEFINITION_HELPER #define LOG_DEFINITION_HELPER(name, defaultColor, severity, visibility) \ template 0)>> \ - NOINLINE inline void name(const LogMessageColor& color, const std::string_view& fmtStr, const TArgs&... args) \ + inline void name(const LogMessageColor& color, const std::string_view& fmtStr, const TArgs&... args) \ { \ - ILogManager::GetInstance().Log(mh::format(fmtStr, args...), color, (severity), (visibility)); \ + detail::log_h::LogImpl(color, (severity), (visibility), fmtStr, args...); \ } \ inline void name(const LogMessageColor& color, std::string msg) \ { \ @@ -118,10 +138,7 @@ namespace tf2_bot_detector template \ NOINLINE inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ { \ - if (fmtStr.empty()) \ - name(color, "{}: {}", location, mh::format(fmtStr, args...)); \ - else \ - name(color, "{}", location); \ + detail::log_h::LogImpl(color, (severity), (visibility), location, fmtStr, args...); \ } \ template \ inline void name(const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ @@ -152,7 +169,7 @@ namespace tf2_bot_detector attr void name(const mh::source_location& location, const std::exception& e, \ const std::string_view& fmtStr, const TArgs&... args) \ { \ - name(location, e, mh::format(fmtStr, args...)); \ + name(location, e, mh::try_format(fmtStr, args...)); \ } LOG_DEFINITION_HELPER(DebugLogException, ); @@ -169,6 +186,6 @@ namespace tf2_bot_detector [[noreturn]] void LogFatalError(const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) { - LogFatalError(location, mh::format(fmtStr, args...)); + LogFatalError(location, mh::try_format(fmtStr, args...)); } } diff --git a/tf2_bot_detector/Networking/HTTPClient.cpp b/tf2_bot_detector/Networking/HTTPClient.cpp index f7be18ba..157438eb 100644 --- a/tf2_bot_detector/Networking/HTTPClient.cpp +++ b/tf2_bot_detector/Networking/HTTPClient.cpp @@ -3,8 +3,6 @@ #include "HTTPClient.h" #include "HTTPHelpers.h" -#include - #pragma warning(push, 1) #include #pragma warning(pop) @@ -24,7 +22,7 @@ std::string HTTPClient::GetString(const URL& url) const auto response = client.Get(url.m_Path.c_str(), headers); if (!response) - throw http_error("Failed to HTTP GET "s << url); + throw http_error(mh::format("Failed to HTTP GET {}", url)); if (response->status >= 400 && response->status < 600) throw http_error(response->status); diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 35573f85..70fae2cc 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -4,14 +4,18 @@ #include "SteamID.h" #include "Version.h" +#include + #include #include #include #include +#include namespace tf2_bot_detector { class HTTPClient; + struct BuildInfo; inline namespace Platform { @@ -22,12 +26,40 @@ namespace tf2_bot_detector std::filesystem::path GetAppDataDir(); std::filesystem::path GetRealAppDataDir(); - /// - /// Is there an update available via a platform-specific update mechanism? - /// msix(bundle) on Windows, APT on linux - /// - std::future> CheckForPlatformUpdate(ReleaseChannel rc, const HTTPClient& client); - void BeginPlatformUpdate(ReleaseChannel rc, const HTTPClient& client); + enum class OS + { + Windows, + Linux, + }; + OS GetOS(); + + enum class Arch + { + x86, + x64, + }; + Arch GetArch(); + + namespace InstallUpdate + { + struct Success {}; + + // We think we've started the update, but there's no way to determine if it succeeded or not. + struct StartedNoFeedback {}; + + struct NeedsUpdateTool + { + std::string m_UpdateToolArgs; + }; + + using Result = std::variant< + Success, + StartedNoFeedback, + NeedsUpdateTool>; + }; + + bool CanInstallUpdate(const BuildInfo& bi); + std::future BeginInstallUpdate(const BuildInfo& bi, const HTTPClient& client); bool IsInstalled(); // As opposed to portable namespace Processes @@ -50,3 +82,13 @@ namespace tf2_bot_detector } } } + +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::Platform::OS) + MH_ENUM_REFLECT_VALUE(Windows) + MH_ENUM_REFLECT_VALUE(Linux) +MH_ENUM_REFLECT_END() + +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::Platform::Arch) + MH_ENUM_REFLECT_VALUE(x86) + MH_ENUM_REFLECT_VALUE(x64) +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector/Platform/Windows/Platform.cpp b/tf2_bot_detector/Platform/Windows/Platform.cpp new file mode 100644 index 00000000..886362b7 --- /dev/null +++ b/tf2_bot_detector/Platform/Windows/Platform.cpp @@ -0,0 +1,35 @@ +#include "Platform/Platform.h" +#include "Log.h" + +#include + +using namespace tf2_bot_detector; + +Platform::OS Platform::GetOS() +{ + return OS::Windows; +} + +Platform::Arch Platform::GetArch() +{ + static const Platform::Arch s_Arch = [] + { + SYSTEM_INFO sysInfo; + GetNativeSystemInfo(&sysInfo); + switch (sysInfo.wProcessorArchitecture) + { + default: + LogError(MH_SOURCE_LOCATION_CURRENT(), + "Failed to identify processor architecture: wProcessorArchitecture = {}", + sysInfo.wProcessorArchitecture); + + [[fallthrough]]; + case PROCESSOR_ARCHITECTURE_INTEL: + return Arch::x86; + case PROCESSOR_ARCHITECTURE_AMD64: + return Arch::x64; + } + }(); + + return s_Arch; +} diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index a23f1870..1db81e9f 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -3,8 +3,10 @@ #include "Networking/HTTPHelpers.h" #include "Util/TextUtils.h" #include "Log.h" +#include "UpdateManager.h" #include "Version.h" +#include #include #include @@ -48,30 +50,36 @@ bool tf2_bot_detector::Platform::IsInstalled() return !!GetAppPackage(); } -std::future> tf2_bot_detector::Platform::CheckForPlatformUpdate( - ReleaseChannel rc, const HTTPClient& client) +bool tf2_bot_detector::Platform::CanInstallUpdate(const BuildInfo& bi) { if (!IsInstalled()) - { - DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Not installed"); - return mh::make_ready_future(std::optional{}); - } + return false; - auto clientPtr = client.shared_from_this(); - - return std::async([rc, clientPtr]() -> std::optional - { - auto result = clientPtr->GetString(mh::format("https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.txt?type={:v}", rc)); - auto version = Version::Parse(result.c_str()); - if (version > VERSION) - return version; + if (bi.m_MSIXBundleURL.empty()) + return false; - return std::nullopt; - }); + return true; } -void tf2_bot_detector::Platform::BeginPlatformUpdate(ReleaseChannel rc, const HTTPClient& client) +std::future tf2_bot_detector::Platform::BeginInstallUpdate( + const BuildInfo& buildInfo, const HTTPClient& client) { + if (!IsInstalled()) + { + constexpr const char ERROR_MSG[] = "Attempted to call " __FUNCTION__ "() when we aren't installed." + " This should never happen."; + LogError(MH_SOURCE_LOCATION_CURRENT(), ERROR_MSG); + throw std::logic_error(ERROR_MSG); + } + + if (buildInfo.m_MSIXBundleURL.empty()) + { + constexpr const char ERROR_MSG[] = "BuildInfo's msix bundle url is empty." + " This should never happen; CanInstallUpdate() should have returned false"; + LogError(MH_SOURCE_LOCATION_CURRENT(), ERROR_MSG); + throw std::invalid_argument(ERROR_MSG); + } + using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; @@ -88,11 +96,14 @@ void tf2_bot_detector::Platform::BeginPlatformUpdate(ReleaseChannel rc, const HT if (appInstallerFound) { - Shell::OpenURL(mh::format( - "ms-appinstaller:?source=https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", rc)); + Shell::OpenURL(mh::format("ms-appinstaller:?source={}", buildInfo.m_MSIXBundleURL)); + return mh::make_ready_future(InstallUpdate::StartedNoFeedback{}); } else { - + return mh::make_ready_future(InstallUpdate::NeedsUpdateTool + { + .m_UpdateToolArgs = mh::format("--msix-bundle-url {}", std::quoted(buildInfo.m_MSIXBundleURL)), + }); } } diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index d0ad8e0b..4aa27e2e 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -1,4 +1,5 @@ #include "Config/Settings.h" +#include "Platform/Platform.h" #include "SetupFlow/ISetupFlowPage.h" #include "UI/ImGui_TF2BotDetector.h" #include "Log.h" @@ -21,6 +22,7 @@ namespace bool CanCommit() const override; void Commit(Settings& settings); + bool WantsSetupText() const override { return false; } bool WantsContinueButton() const override { return false; } private: @@ -41,11 +43,23 @@ namespace { m_HasCheckedForUpdate = true; - ImGui::NewLine(); - const UpdateStatus updateStatus = m_LastUpdateStatus = ds.m_UpdateManager->GetUpdateStatus(); const IAvailableUpdate* update = ds.m_UpdateManager->GetAvailableUpdate(); + ImGui::TextFmt("Update Check"); + ImGui::NewLine(); + ImGui::Separator(); + + ImGui::NewLine(); + + if (auto rc = ds.m_Settings->m_ReleaseChannel; Combo("##SetupFlow_UpdateCheckingMode", rc)) + { + ds.m_Settings->m_ReleaseChannel = rc; + ds.m_UpdateManager->QueueUpdateCheck(); + } + + ImGui::NewLine(); + bool drawContinueWithoutUpdating = false; switch (updateStatus) @@ -76,13 +90,13 @@ namespace break; case UpdateStatus::UpToDate: ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, - ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public)); + mh::enum_fmt(ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public))); return OnDrawResult::EndDrawing; case UpdateStatus::UpdateAvailable: { ImGui::TextFmt({ 0, 1, 1, 1 }, "Update available: v{} {:v} (current version v{})", - update->GetVersion(), update->GetReleaseChannel(), VERSION); + update->m_State.m_Version, mh::enum_fmt(update->m_State.m_ReleaseChannel), VERSION); drawContinueWithoutUpdating = true; ImGui::NewLine(); @@ -102,6 +116,15 @@ namespace ImGui::SameLine(); + ImGui::EnabledSwitch(!update->m_State.m_GitHubURL.empty(), [&] + { + if (ImGui::Button("View on GitHub")) + Shell::OpenURL(update->m_State.m_GitHubURL); + + }, "Unable to determine GitHub URL"); + + ImGui::SameLine(); + break; } diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index ff30bc32..d750b2d4 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -2,312 +2,415 @@ #include "Config/Settings.h" #include "Networking/GithubAPI.h" #include "Networking/HTTPClient.h" +#include "Networking/HTTPHelpers.h" #include "Platform/Platform.h" +#include "Util/JSONUtils.h" #include "Log.h" #include "ReleaseChannel.h" #include #include +#include #include +#include +#include +#include #include +using namespace std::string_view_literals; using namespace tf2_bot_detector; -namespace +namespace tf2_bot_detector { - class BaseUpdateCheck + void to_json(nlohmann::json& j, const Platform::OS& d) { - public: - BaseUpdateCheck(ReleaseChannel rc, const HTTPClient& client); - virtual ~BaseUpdateCheck() = default; - - virtual void Update() = 0; - virtual UpdateStatus GetUpdateStatus() const = 0; - const IAvailableUpdate* GetAvailableUpdate() const; - - protected: - virtual bool CanSelfUpdate() const = 0; - virtual void BeginSelfUpdateImpl() const = 0; - const HTTPClient& GetHTTPClient() const { return *m_Client; } - - struct AvailableUpdate final : public IAvailableUpdate - { - AvailableUpdate(BaseUpdateCheck& parent); - - ReleaseChannel m_ReleaseChannel{}; - ReleaseChannel GetReleaseChannel() const override final { return m_ReleaseChannel; } - - Version m_Version{}; - Version GetVersion() const override final { return m_Version; } - - bool CanSelfUpdate() const override { return m_Parent.CanSelfUpdate(); } - void BeginSelfUpdate() const override final; - - private: - BaseUpdateCheck& m_Parent; - - } m_AvailableUpdate; - - private: - std::shared_ptr m_Client; - }; - - BaseUpdateCheck::BaseUpdateCheck(ReleaseChannel rc, const HTTPClient& client) : - m_AvailableUpdate(*this), - m_Client(client.shared_from_this()) + j = mh::find_enum_value_name(d); + } + void from_json(const nlohmann::json& j, Platform::OS& d) { - m_AvailableUpdate.m_ReleaseChannel = rc; + mh::find_enum_value(j, d); } - const IAvailableUpdate* BaseUpdateCheck::GetAvailableUpdate() const + void to_json(nlohmann::json& j, const Platform::Arch& d) { - return m_AvailableUpdate.m_Version > VERSION ? &m_AvailableUpdate : nullptr; + j = mh::find_enum_value_name(d); } - - BaseUpdateCheck::AvailableUpdate::AvailableUpdate(BaseUpdateCheck& parent) : - m_Parent(parent) + void from_json(const nlohmann::json& j, Platform::Arch& d) { + mh::find_enum_value(j, d); } - void BaseUpdateCheck::AvailableUpdate::BeginSelfUpdate() const + void to_json(nlohmann::json& j, const BuildInfo::BuildVariant& d) { - if (!CanSelfUpdate()) - throw std::logic_error("BeginSelfUpdate() called when CanSelfUpdate() returned false"); + j = + { + { "os", d.m_OS }, + { "arch", d.m_Arch }, + { "download_url", d.m_DownloadURL }, + }; + } + void from_json(const nlohmann::json& j, BuildInfo::BuildVariant& d) + { + d.m_OS = j.at("os"); + d.m_Arch = j.at("arch"); + d.m_DownloadURL = j.at("download_url"); + } - return m_Parent.BeginSelfUpdateImpl(); + void to_json(nlohmann::json& j, const BuildInfo& d) + { + j = + { + { "version", d.m_Version }, + { "build_type", d.m_ReleaseChannel }, + { "github_url", d.m_GitHubURL }, + { "msix_bundle_url", d.m_MSIXBundleURL }, + { "updater", d.m_Updater }, + { "portable", d.m_Portable }, + }; + } + void from_json(const nlohmann::json& j, BuildInfo& d) + { + d.m_Version = j.at("version"); + d.m_ReleaseChannel = j.at("build_type"); + try_get_to_defaulted(j, d.m_GitHubURL, "github_url"); + try_get_to_defaulted(j, d.m_MSIXBundleURL, "msix_bundle_url"); + j.at("updater").get_to(d.m_Updater); + j.at("portable").get_to(d.m_Portable); } } namespace { - class PlatformUpdateCheck final : public BaseUpdateCheck + class UpdateManager final : public IUpdateManager { + struct AvailableUpdate final : IAvailableUpdate + { + AvailableUpdate(UpdateManager& parent, BuildInfo&& buildInfo); + + bool CanSelfUpdate() const override; + void BeginSelfUpdate() const override; + + UpdateManager& m_Parent; + }; + public: - PlatformUpdateCheck(ReleaseChannel rc, const HTTPClient& client); + UpdateManager(const Settings& settings); void Update() override; UpdateStatus GetUpdateStatus() const override; + std::string GetErrorString() const override; + const AvailableUpdate* GetAvailableUpdate() const override; - protected: - bool CanSelfUpdate() const override { return true; } - void BeginSelfUpdateImpl() const override; + void QueueUpdateCheck() override { m_IsUpdateQueued = true; } private: - std::variant, std::exception_ptr> m_IsPlatformUpdateAvailable; - std::future> m_CheckForPlatformUpdateTask; + const Settings& m_Settings; + + struct BaseExceptionData + { + BaseExceptionData(const std::type_info& type, std::string message, const std::exception_ptr& exception); + + const std::type_info& m_Type; + std::string m_Message; + std::exception_ptr m_Exception; + }; + + template + struct ExceptionData : BaseExceptionData + { + using BaseExceptionData::BaseExceptionData; + }; + + struct DownloadedUpdateTool + { + std::filesystem::path m_Path; + std::string m_Arguments; + }; + + struct UpdateToolResult + { + bool m_Success{}; + }; + struct UpdateToolExceptionData : BaseExceptionData + { + using BaseExceptionData::BaseExceptionData; + }; + + using UpdateCheckState_t = std::variant, AvailableUpdate, ExceptionData>; + UpdateCheckState_t m_UpdateCheckState; + + using State_t = std::variant< + std::monostate, + + std::future, + InstallUpdate::Result, + ExceptionData, + + std::future, + DownloadedUpdateTool, + ExceptionData, + + std::future, + UpdateToolResult, + ExceptionData + + >; + State_t m_State; + + template bool UpdateFutureState(); + + bool CanReplaceUpdateCheckState() const; + + bool m_IsUpdateQueued = true; + bool m_IsInstalled; }; - PlatformUpdateCheck::PlatformUpdateCheck(ReleaseChannel rc, const HTTPClient& client) : - BaseUpdateCheck(rc, client) + UpdateManager::UpdateManager(const Settings& settings) : + m_Settings(settings), + m_IsInstalled(Platform::IsInstalled()) { - m_CheckForPlatformUpdateTask = Platform::CheckForPlatformUpdate(rc, client); } - void PlatformUpdateCheck::Update() + template + bool UpdateManager::UpdateFutureState() { - if (mh::is_future_ready(m_CheckForPlatformUpdateTask)) + if (auto future = std::get_if>(&m_State)) { try { - auto version = m_CheckForPlatformUpdateTask.get(); - m_IsPlatformUpdateAvailable = version; - - if (version) - m_AvailableUpdate.m_Version = *version; + if (mh::is_future_ready(*future)) + m_State.emplace(future->get()); } catch (const std::exception& e) { - LogException(MH_SOURCE_LOCATION_CURRENT(), e, - "Failed to retrieve result of platform update availability check"); - m_IsPlatformUpdateAvailable = std::current_exception(); + m_State.emplace>(typeid(e), e.what(), std::current_exception()); } - } - } - UpdateStatus PlatformUpdateCheck::GetUpdateStatus() const - { - if (std::holds_alternative(m_IsPlatformUpdateAvailable)) - return UpdateStatus::Checking; - else if (const auto* value = std::get_if>(&m_IsPlatformUpdateAvailable)) - return *value ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; - else if (std::holds_alternative(m_IsPlatformUpdateAvailable)) - return UpdateStatus::CheckFailed; - else - { - LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown variant index {}", m_IsPlatformUpdateAvailable.index()); - return UpdateStatus::CheckFailed; + return true; } - } - void PlatformUpdateCheck::BeginSelfUpdateImpl() const - { - auto update = GetAvailableUpdate(); - if (!update) - { - LogError(MH_SOURCE_LOCATION_CURRENT(), "BeginSelfUpdateImpl() called when GetAvailableUpdate() returned nullptr"); - return; - } - - Platform::BeginPlatformUpdate(update->GetReleaseChannel(), GetHTTPClient()); + return false; } -} -namespace -{ - class PortableUpdateCheck final : public BaseUpdateCheck + void UpdateManager::Update() { - public: - PortableUpdateCheck(ReleaseChannel rc, const HTTPClient& client); + if (m_IsUpdateQueued && CanReplaceUpdateCheckState()) + { + auto client = m_Settings.GetHTTPClient(); + const auto releaseChannel = m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::None); + if (client && (releaseChannel != ReleaseChannel::None)) + { + auto sharedClient = client->shared_from_this(); + m_UpdateCheckState = std::async([sharedClient, releaseChannel]() -> BuildInfo + { + const mh::fmtstr<256> url( + "https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.json?type={:v}", + mh::enum_fmt(releaseChannel)); - void Update() override; - UpdateStatus GetUpdateStatus() const override; + DebugLog("HTTP GET {}", url); + auto response = sharedClient->GetString(url.view()); - protected: - bool CanSelfUpdate() const override { return false; } - void BeginSelfUpdateImpl() const override; + auto json = nlohmann::json::parse(response); - private: - std::variant< - std::future, - GithubAPI::NewVersionResult, - std::exception_ptr> - m_NewVersionResult; - }; + return json.get(); + }); - PortableUpdateCheck::PortableUpdateCheck(ReleaseChannel rc, const HTTPClient& client) : - BaseUpdateCheck(rc, client) - { - m_NewVersionResult = GithubAPI::CheckForNewVersion(client); - } + m_IsUpdateQueued = false; + } + } - void PortableUpdateCheck::Update() - { - if (auto future = std::get_if>(&m_NewVersionResult); - future && mh::is_future_ready(*future)) + if (auto future = std::get_if>(&m_UpdateCheckState)) { try { - auto versionCheckResult = future->get(); - m_NewVersionResult = versionCheckResult; - - if (m_AvailableUpdate.m_ReleaseChannel == ReleaseChannel::Preview && - versionCheckResult.IsPreviewAvailable()) - { - m_AvailableUpdate.m_Version = versionCheckResult.m_Preview->m_Version; - } - else if (mh::any_eq(m_AvailableUpdate.m_ReleaseChannel, ReleaseChannel::Preview, ReleaseChannel::Public) && - versionCheckResult.IsReleaseAvailable()) - { - m_AvailableUpdate.m_Version = versionCheckResult.m_Stable->m_Version; - } + if (mh::is_future_ready(*future)) + m_UpdateCheckState.emplace(*this, future->get()); } catch (const std::exception& e) { - LogException(MH_SOURCE_LOCATION_CURRENT(), e, - "Failed to retrieve result of platform update availability check"); - m_NewVersionResult = std::current_exception(); + m_UpdateCheckState.emplace>(typeid(e), e.what(), std::current_exception()); } } + + UpdateFutureState() || + UpdateFutureState() || + UpdateFutureState(); } - UpdateStatus PortableUpdateCheck::GetUpdateStatus() const + UpdateStatus UpdateManager::GetUpdateStatus() const { - if (std::holds_alternative>(m_NewVersionResult)) + if (m_IsUpdateQueued) { - return UpdateStatus::Checking; + if (m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::None) == ReleaseChannel::None) + return UpdateStatus::UpdateCheckDisabled; + else if (!m_Settings.GetHTTPClient()) + return UpdateStatus::InternetAccessDisabled; + else + return UpdateStatus::CheckQueued; } - else if (std::holds_alternative(m_NewVersionResult)) + + switch (m_State.index()) + { + case mh::variant_type_index_v: + break; + + ////////////////////////////// + /// InstallUpdate::Result /// + ////////////////////////////// + case mh::variant_type_index_v>: + return UpdateStatus::Updating; + case mh::variant_type_index_v>: + return UpdateStatus::UpdateFailed; + case mh::variant_type_index_v: { - return m_AvailableUpdate.m_Version > VERSION ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; + auto& result = std::get(m_State); + switch (result.index()) + { + case mh::variant_type_index_v: + return UpdateStatus::UpdateSuccess; + + default: + LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown InstallUpdate::Result index {}", result.index()); + case mh::variant_type_index_v: + return UpdateStatus::Updating; + case mh::variant_type_index_v: + return UpdateStatus::UpdateToolDownloading; + } } - else if (std::holds_alternative(m_NewVersionResult)) + + //////////////////////////// + /// DownloadedUpdateTool /// + //////////////////////////// + case mh::variant_type_index_v>: + return UpdateStatus::UpdateToolDownloading; + case mh::variant_type_index_v>: + return UpdateStatus::UpdateToolDownloadingFailed; + case mh::variant_type_index_v: + return UpdateStatus::Updating; + + //////////////////////// + /// UpdateToolResult /// + //////////////////////// + case mh::variant_type_index_v>: + return UpdateStatus::Updating; + case mh::variant_type_index_v>: + return UpdateStatus::UpdateFailed; + case mh::variant_type_index_v: { + auto& result = std::get(m_State); + return result.m_Success ? UpdateStatus::UpdateSuccess : UpdateStatus::UpdateFailed; + } + } + + switch (m_UpdateCheckState.index()) + { + case mh::variant_type_index_v>: + return UpdateStatus::Checking; + case mh::variant_type_index_v>: return UpdateStatus::CheckFailed; + case mh::variant_type_index_v: + { + auto& update = std::get(m_UpdateCheckState); + return update.m_State.m_Version > VERSION ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; + } } - LogError(MH_SOURCE_LOCATION_CURRENT(), "{}", UpdateStatus::InternalError_UnknownVariantState); - return UpdateStatus::InternalError_UnknownVariantState; + LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown update state: m_UpdateCheckState = {}, m_State = {}", + m_UpdateCheckState.index(), m_State.index()); + return UpdateStatus::Unknown; } - void PortableUpdateCheck::BeginSelfUpdateImpl() const + std::string UpdateManager::GetErrorString() const { - throw std::logic_error("Portable self-updating not currently supported"); + return std::visit([](const auto& value) -> std::string + { + using type_t = std::decay_t; + if constexpr (std::is_base_of_v) + { + const BaseExceptionData& d = value; + return mh::format("{}: {}"sv, d.m_Type.name(), d.m_Message); + } + else + { + return {}; + } + + }, m_State); } -} -namespace -{ - class UpdateManager final : public IUpdateManager + auto UpdateManager::GetAvailableUpdate() const -> const AvailableUpdate* { - public: - UpdateManager(const Settings& settings); + if (GetUpdateStatus() == UpdateStatus::UpdateAvailable) + return std::get_if(&m_UpdateCheckState); - void Update() override; - UpdateStatus GetUpdateStatus() const override; + return nullptr; + } - const IAvailableUpdate* GetAvailableUpdate() const override; + /// + /// Is it safe to replace the value of m_State? (make sure we won't block the thread) + /// + bool UpdateManager::CanReplaceUpdateCheckState() const + { + const auto* future = std::get_if>(&m_UpdateCheckState); + if (!future) + return true; // some other value in the variant - void QueueUpdateCheck() override { m_IsUpdateQueued = true; } + if (!future->valid()) + return true; // future is empty - private: - const Settings& m_Settings; + if (mh::is_future_ready(*future)) + return true; // future is ready - bool m_IsUpdateQueued = true; - bool m_IsInstalled; - std::unique_ptr m_UpdateCheck; - }; + return false; + } - UpdateManager::UpdateManager(const Settings& settings) : - m_Settings(settings), - m_IsInstalled(Platform::IsInstalled()) + UpdateManager::AvailableUpdate::AvailableUpdate(UpdateManager& parent, BuildInfo&& buildInfo) : + IAvailableUpdate(std::move(buildInfo)), + m_Parent(parent) { } - void UpdateManager::Update() + bool UpdateManager::AvailableUpdate::CanSelfUpdate() const { - if (m_IsUpdateQueued) - { - auto client = m_Settings.GetHTTPClient(); - if (client && (m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::None) != ReleaseChannel::None)) - { - if (m_IsInstalled) - m_UpdateCheck = std::make_unique(*m_Settings.m_ReleaseChannel, *client); - else - m_UpdateCheck = std::make_unique(*m_Settings.m_ReleaseChannel, *client); + if (!m_Parent.m_Settings.GetHTTPClient()) + return false; - m_IsUpdateQueued = false; - } - } + if (Platform::IsInstalled()) + return Platform::CanInstallUpdate(m_State); - if (m_UpdateCheck) - m_UpdateCheck->Update(); + // If we're portable, we just need to pass some arguments + static const bool s_WarnOnce = [] + { + LogWarning(MH_SOURCE_LOCATION_CURRENT(), "TODO: portable self-update not yet implemented"); + return false; + }(); + + return false; } - UpdateStatus UpdateManager::GetUpdateStatus() const + void UpdateManager::AvailableUpdate::BeginSelfUpdate() const { - if (m_IsUpdateQueued) + if (!CanSelfUpdate()) { - if (m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::None) == ReleaseChannel::None) - return UpdateStatus::UpdateCheckDisabled; - else if (!m_Settings.GetHTTPClient()) - return UpdateStatus::InternetAccessDisabled; - else - return UpdateStatus::CheckQueued; + LogError(MH_SOURCE_LOCATION_CURRENT(), "{} called when CanSelfUpdate() returned false", __func__); + return; } - if (m_UpdateCheck) - return m_UpdateCheck->GetUpdateStatus(); + auto client = m_Parent.m_Settings.GetHTTPClient(); + if (!client) + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "client was nullptr"); + std::terminate(); + } - return UpdateStatus::InternalError_UpdateCheckNull; + m_Parent.m_State = Platform::BeginInstallUpdate(m_State, *client); } - const IAvailableUpdate* UpdateManager::GetAvailableUpdate() const + UpdateManager::BaseExceptionData::BaseExceptionData(const std::type_info& type, std::string message, + const std::exception_ptr& exception) : + m_Type(type), m_Message(std::move(message)), m_Exception(exception) { - if (m_UpdateCheck) - return m_UpdateCheck->GetAvailableUpdate(); - - return nullptr; } } diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index 5d004190..264b8cc8 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -1,5 +1,6 @@ #pragma once +#include "Platform/Platform.h" #include "ReleaseChannel.h" #include "Version.h" @@ -11,25 +12,44 @@ namespace tf2_bot_detector { class Settings; + struct BuildInfo + { + ReleaseChannel m_ReleaseChannel{}; + Version m_Version{}; + + std::string m_GitHubURL; + std::string m_MSIXBundleURL; + + struct BuildVariant + { + Platform::OS m_OS{}; + Platform::Arch m_Arch{}; + std::string m_DownloadURL; + }; + + std::vector m_Updater; + std::vector m_Portable; + }; +} + +namespace tf2_bot_detector +{ class IAvailableUpdate { public: + IAvailableUpdate(BuildInfo&& bi) : m_State(std::move(bi)) {} virtual ~IAvailableUpdate() = default; - virtual ReleaseChannel GetReleaseChannel() const = 0; - virtual Version GetVersion() const = 0; - virtual bool CanSelfUpdate() const = 0; virtual void BeginSelfUpdate() const = 0; + + BuildInfo m_State; }; enum class UpdateStatus { Unknown = 0, - InternalError_UpdateCheckNull, - InternalError_UnknownVariantState, - UpdateCheckDisabled, InternetAccessDisabled, @@ -40,6 +60,9 @@ namespace tf2_bot_detector UpToDate, UpdateAvailable, + UpdateToolDownloading, + UpdateToolDownloadingFailed, + Updating, UpdateFailed, @@ -56,8 +79,9 @@ namespace tf2_bot_detector virtual void QueueUpdateCheck() = 0; virtual void Update() = 0; - virtual UpdateStatus GetUpdateStatus() const = 0; + virtual UpdateStatus GetUpdateStatus() const = 0; + virtual std::string GetErrorString() const = 0; virtual const IAvailableUpdate* GetAvailableUpdate() const = 0; }; } @@ -65,8 +89,6 @@ namespace tf2_bot_detector MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(Unknown) - MH_ENUM_REFLECT_VALUE(InternalError_UpdateCheckNull) - MH_ENUM_REFLECT_VALUE(UpdateCheckDisabled) MH_ENUM_REFLECT_VALUE(InternetAccessDisabled) diff --git a/tf2_bot_detector/Util/JSONUtils.h b/tf2_bot_detector/Util/JSONUtils.h index 03d724ff..f2c8db2a 100644 --- a/tf2_bot_detector/Util/JSONUtils.h +++ b/tf2_bot_detector/Util/JSONUtils.h @@ -84,7 +84,7 @@ namespace tf2_bot_detector } catch (const std::exception& e) { - LogError(std::string(__FUNCTION__) << ": Exception when getting " << name << ": " << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Exception when getting {}", std::quoted(name)); } defaultValFunc(value); @@ -168,7 +168,7 @@ namespace tf2_bot_detector } catch (const std::exception& e) { - LogError(std::string(__FUNCTION__) << ": Exception when getting " << name << ": " << e.what()); + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Exception when getting {}", std::quoted(name)); } value.reset(); diff --git a/tf2_bot_detector/Version.base.h b/tf2_bot_detector/Version.base.h index 7fa3973e..05c391d5 100644 --- a/tf2_bot_detector/Version.base.h +++ b/tf2_bot_detector/Version.base.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -38,6 +40,9 @@ namespace tf2_bot_detector ${CMAKE_PROJECT_VERSION_PATCH}, ${CMAKE_PROJECT_VERSION_TWEAK} ); + + void to_json(nlohmann::json& j, const Version& d); + void from_json(const nlohmann::json& j, Version& d); } template diff --git a/tf2_bot_detector/Version.cpp b/tf2_bot_detector/Version.cpp index c8643f2d..5766d864 100644 --- a/tf2_bot_detector/Version.cpp +++ b/tf2_bot_detector/Version.cpp @@ -1,5 +1,8 @@ #include "Version.h" +#include +#include + using namespace tf2_bot_detector; std::optional Version::Parse(const char* str) @@ -11,3 +14,12 @@ std::optional Version::Parse(const char* str) return v; } + +void tf2_bot_detector::to_json(nlohmann::json& j, const Version& d) +{ + j = mh::fmtstr<128>("{}", d).view(); +} +void tf2_bot_detector::from_json(const nlohmann::json& j, Version& d) +{ + d = Version::Parse(j.get().c_str()).value(); +} From cfd53d6ff6917912f396603ca2e4490ce5e0f352 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 01:48:17 -0700 Subject: [PATCH 095/161] Removed references to mh/exception/error_code_exception.hpp --- submodules/mh_stuff | 2 +- tf2_bot_detector/Platform/Windows/PlatformInstall.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index be2329dc..cff3a662 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit be2329dc040a6e89087b4179816ed11226f41372 +Subproject commit cff3a6624abddb9add8bdc7941323ad3eceefe1a diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index 1db81e9f..f1ac7410 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -6,7 +6,6 @@ #include "UpdateManager.h" #include "Version.h" -#include #include #include From f150eb36aca47728aaa465ca6430092c690aecb5 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 02:32:40 -0700 Subject: [PATCH 096/161] Much saner "latching" mechanism for the update check setup flow page --- tf2_bot_detector/SetupFlow/SetupFlow.cpp | 5 ++ .../SetupFlow/UpdateCheckPage.cpp | 63 ++++++++++++++----- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/tf2_bot_detector/SetupFlow/SetupFlow.cpp b/tf2_bot_detector/SetupFlow/SetupFlow.cpp index 5c4263bb..7f5f6362 100644 --- a/tf2_bot_detector/SetupFlow/SetupFlow.cpp +++ b/tf2_bot_detector/SetupFlow/SetupFlow.cpp @@ -104,6 +104,7 @@ bool SetupFlow::OnDraw(Settings& settings, const ISetupFlowPage::DrawState& ds) drewPage = true; const bool canCommit = page->CanCommit(); + ImGui::EnabledSwitch(canCommit, [&] { if ((wantsContinueButton && ImGui::Button(hasNextPage ? "Next >" : "Done")) || @@ -115,6 +116,10 @@ bool SetupFlow::OnDraw(Settings& settings, const ISetupFlowPage::DrawState& ds) settings.SaveFile(); } + // If this assertion is hit, the page is reporting that it can commit, but we are failing validation. + // This means that we'll be closing and reopening the page every frame. + assert(page->ValidateSettings(settings) != ISetupFlowPage::ValidateSettingsResult::TriggerOpen); + m_ActivePage = INVALID_PAGE; if (!hasNextPage) m_ShouldDraw = false; diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index 4aa27e2e..86c1f2a4 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -27,16 +27,21 @@ namespace private: UpdateStatus m_LastUpdateStatus = UpdateStatus::Unknown; + bool m_HasChangedReleaseChannel = false; bool m_HasCheckedForUpdate = false; bool m_UpdateButtonPressed = false; }; auto UpdateCheckPage::ValidateSettings(const Settings& settings) const -> ValidateSettingsResult { - if (settings.GetHTTPClient() && !m_HasCheckedForUpdate) - return ValidateSettingsResult::TriggerOpen; - - return ValidateSettingsResult::Success; + if (m_HasCheckedForUpdate) + return ValidateSettingsResult::Success; + if (!settings.GetHTTPClient()) + return ValidateSettingsResult::Success; + if (settings.m_ReleaseChannel.value_or(ReleaseChannel::None) == ReleaseChannel::None) + return ValidateSettingsResult::Success; + + return ValidateSettingsResult::TriggerOpen; } auto UpdateCheckPage::OnDraw(const DrawState& ds) -> OnDrawResult @@ -56,22 +61,36 @@ namespace { ds.m_Settings->m_ReleaseChannel = rc; ds.m_UpdateManager->QueueUpdateCheck(); + m_HasChangedReleaseChannel = true; } ImGui::NewLine(); - bool drawContinueWithoutUpdating = false; + enum class ContinueButtonMode + { + None, + Continue, + ContinueWithoutUpdating, + + } continueButtonMode = ContinueButtonMode::None; switch (updateStatus) { case UpdateStatus::Unknown: ImGui::TextFmt({ 1, 1, 0, 1 }, "Unknown"); - drawContinueWithoutUpdating = true; + continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; break; case UpdateStatus::UpdateCheckDisabled: + { ImGui::TextFmt("Automatic update checks disabled by user"); - return OnDrawResult::EndDrawing; + if (!m_HasChangedReleaseChannel) + return OnDrawResult::EndDrawing; + + ImGui::NewLine(); + continueButtonMode = ContinueButtonMode::Continue; + break; + } case UpdateStatus::InternetAccessDisabled: ImGui::TextFmt("Internet connectivity disabled by user"); return OnDrawResult::EndDrawing; @@ -85,19 +104,26 @@ namespace case UpdateStatus::CheckFailed: ImGui::TextFmt({ 1, 1, 0, 1 }, "Update check failed"); - drawContinueWithoutUpdating = true; + continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; ImGui::NewLine(); break; case UpdateStatus::UpToDate: + { ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, mh::enum_fmt(ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public))); - return OnDrawResult::EndDrawing; + if (!m_HasChangedReleaseChannel) + return OnDrawResult::EndDrawing; + + ImGui::NewLine(); + continueButtonMode = ContinueButtonMode::Continue; + + break; + } case UpdateStatus::UpdateAvailable: { ImGui::TextFmt({ 0, 1, 1, 1 }, "Update available: v{} {:v} (current version v{})", update->m_State.m_Version, mh::enum_fmt(update->m_State.m_ReleaseChannel), VERSION); - drawContinueWithoutUpdating = true; ImGui::NewLine(); @@ -124,7 +150,7 @@ namespace }, "Unable to determine GitHub URL"); ImGui::SameLine(); - + continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; break; } @@ -145,10 +171,15 @@ namespace break; } - if (drawContinueWithoutUpdating) + if (continueButtonMode == ContinueButtonMode::ContinueWithoutUpdating && + ImGui::Button("Continue without updating >")) { - if (ImGui::Button("Continue without updating >")) - return OnDrawResult::EndDrawing; + return OnDrawResult::EndDrawing; + } + if (continueButtonMode == ContinueButtonMode::Continue && + ImGui::Button("Continue >")) + { + return OnDrawResult::EndDrawing; } return OnDrawResult::ContinueDrawing; @@ -156,11 +187,15 @@ namespace void UpdateCheckPage::Init(const Settings& settings) { + m_HasChangedReleaseChannel = false; m_UpdateButtonPressed = false; } bool UpdateCheckPage::CanCommit() const { + if (!m_HasCheckedForUpdate) + return false; + return mh::none_eq(m_LastUpdateStatus , UpdateStatus::CheckQueued , UpdateStatus::Checking From d7fc880e706ca3726b351bdd9af53872023409ff Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 02:48:29 -0700 Subject: [PATCH 097/161] Emit a log message on exception in the updatemanager state machine --- tf2_bot_detector/UpdateManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index d750b2d4..117fea24 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -184,6 +184,7 @@ namespace } catch (const std::exception& e) { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, __FUNCSIG__); m_State.emplace>(typeid(e), e.what(), std::current_exception()); } From fcd62a2994c964d46834223f618fc20e7f75d38b Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 02:48:58 -0700 Subject: [PATCH 098/161] here too --- tf2_bot_detector/UpdateManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 117fea24..10606e37 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -230,6 +230,7 @@ namespace } catch (const std::exception& e) { + LogException(MH_SOURCE_LOCATION_CURRENT(), e, __FUNCSIG__); m_UpdateCheckState.emplace>(typeid(e), e.what(), std::current_exception()); } } From 4d2acb24a8fc98252833e7eb3f3d99b9f84aaf27 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 03:35:27 -0700 Subject: [PATCH 099/161] Try invoking the ms-appinstaller uri handler instead of checking for the package --- .../Platform/Windows/PlatformInstall.cpp | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index f1ac7410..d66ffb12 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -79,30 +80,30 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat throw std::invalid_argument(ERROR_MSG); } - using namespace winrt::Windows::ApplicationModel; - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Foundation::Collections; - using namespace winrt::Windows::Management::Deployment; + using winrt::Windows::ApplicationModel::Package; + using winrt::Windows::Foundation::Uri; + using winrt::Windows::System::Launcher; - PackageManager mgr; - auto appInstallerPackages = mgr.FindPackages(L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - bool appInstallerFound = false; - for (const Package& pkg : appInstallerPackages) - { - appInstallerFound = true; - break; - } + const auto bundleUri = buildInfo.m_MSIXBundleURL; - if (appInstallerFound) - { - Shell::OpenURL(mh::format("ms-appinstaller:?source={}", buildInfo.m_MSIXBundleURL)); - return mh::make_ready_future(InstallUpdate::StartedNoFeedback{}); - } - else - { - return mh::make_ready_future(InstallUpdate::NeedsUpdateTool + return std::async([bundleUri]() -> InstallUpdate::Result + { + const Uri uri = mh::format(L"ms-appinstaller:?source={}", ToWC(bundleUri)); + + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Attempting to LaunchUriAsync for {}", ToMB(uri.ToString())); + auto result = Launcher::LaunchUriAsync(uri); + + if (result.get()) { - .m_UpdateToolArgs = mh::format("--msix-bundle-url {}", std::quoted(buildInfo.m_MSIXBundleURL)), - }); - } + return InstallUpdate::StartedNoFeedback{}; + } + else + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "LaunchUriAsync returned false, requesting update tool"); + return InstallUpdate::NeedsUpdateTool + { + .m_UpdateToolArgs = mh::format("--msix-bundle-url {}", std::quoted(bundleUri)), + }; + } + }); } From fd9aeca6df7cf6e72b6a11f3661027c87a3e5eff Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 12:30:05 -0700 Subject: [PATCH 100/161] test replacement of vcpkg_setup.bat and response file with standardized vcpkg.json --- .github/workflows/ccpp.yml | 6 +++--- vcpkg.json | 16 ++++++++++++++++ vcpkg_packages.txt | 10 ---------- vcpkg_setup.bat | 38 +------------------------------------- 4 files changed, 20 insertions(+), 50 deletions(-) create mode 100644 vcpkg.json delete mode 100644 vcpkg_packages.txt diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f5077463..7c6443b2 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -93,12 +93,12 @@ jobs: - name: run-vcpkg uses: lukka/run-vcpkg@v3.2 env: - VCPKG_RESPONSE_FILE: '${{ github.workspace }}/vcpkg_packages.txt' + VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' with: vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - vcpkgArguments: '@${{ env.VCPKG_RESPONSE_FILE }}' + vcpkgArguments: '--dry-run' vcpkgTriplet: ${{ matrix.triplet }} - appendedCacheKey: ${{ hashFiles(env.VCPKG_RESPONSE_FILE) }} + appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }} - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 00000000..840a4d45 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,16 @@ +{ + "name": "tf2-bot-detector", + "version-string": "", + "dependencies": [ + "sdl2", + "openssl", + "glbinding", + "cpp-httplib", + "libzippp", + "fmt", + "discord-game-sdk", + "stb", + "nlohmann-json", + "catch2" + ] +} diff --git a/vcpkg_packages.txt b/vcpkg_packages.txt deleted file mode 100644 index bea37415..00000000 --- a/vcpkg_packages.txt +++ /dev/null @@ -1,10 +0,0 @@ -SDL2 -openSSL -glbinding -cpp-httplib -libzippp -fmt -discord-game-sdk -stb -nlohmann-json -catch2 diff --git a/vcpkg_setup.bat b/vcpkg_setup.bat index 75901626..b190b5bd 100644 --- a/vcpkg_setup.bat +++ b/vcpkg_setup.bat @@ -1,39 +1,3 @@ @ECHO OFF -PUSHD submodules\vcpkg - SET VCPKG_ROOT=%CD% - ECHO VCPKG_ROOT = %VCPKG_ROOT% -POPD - -IF EXIST "%ProgramFiles(x86)%" ( - SET VCPKG_DEFAULT_TRIPLET=x64-windows -) else ( - SET VCPKG_DEFAULT_TRIPLET=x86-windows -) -ECHO VCPKG_DEFAULT_TRIPLET = %VCPKG_DEFAULT_TRIPLET% - -SET VCPKG_EXE=%VCPKG_ROOT%\vcpkg.exe - -SETLOCAL - -IF NOT EXIST "%VCPKG_EXE%" ( - ECHO Building vcpkg... - CALL %VCPKG_ROOT%\bootstrap-vcpkg.bat - IF %ERRORLEVEL% NEQ 0 EXIT /B -) - -ECHO Installing missing packages... - %VCPKG_EXE% install @vcpkg_packages.txt - IF "%VCPKG_DEFAULT_TRIPLET%"=="x64-windows" ( - if [%1]==[/MULTIARCH] ( - %VCPKG_EXE% install @vcpkg_packages.txt --triplet x86-windows - ) - ) - - IF %ERRORLEVEL% NEQ 0 EXIT /B - -ECHO Upgrading existing packages... - %VCPKG_EXE% upgrade --no-dry-run - IF %ERRORLEVEL% NEQ 0 EXIT /B - -ENDLOCAL +ECHO vcpkg_setup.bat is deprecated and no longer necessary. It is recommended to remove it from your build script. From b1e9b77cb4ff2d3d5d56e8044a09db4db846f5b8 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 12:35:57 -0700 Subject: [PATCH 101/161] stop vcpkg from complaining --- .github/workflows/ccpp.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7c6443b2..1c491675 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -94,9 +94,10 @@ jobs: uses: lukka/run-vcpkg@v3.2 env: VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' + VCPKG_FEATURE_FLAGS: manifests with: vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - vcpkgArguments: '--dry-run' + vcpkgArguments: 'fmt --dry-run' vcpkgTriplet: ${{ matrix.triplet }} appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }} From f17dda50faeae73c582077ef11d4c665781dcfde Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 12:43:04 -0700 Subject: [PATCH 102/161] disable manifests during run-vcpkg --- .github/workflows/ccpp.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 1c491675..cd7d03f9 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -94,7 +94,7 @@ jobs: uses: lukka/run-vcpkg@v3.2 env: VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' - VCPKG_FEATURE_FLAGS: manifests + VCPKG_FEATURE_FLAGS: -manifests with: vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' vcpkgArguments: 'fmt --dry-run' @@ -109,6 +109,8 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} shell: bash + env: + VCPKG_FEATURE_FLAGS: manifests run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" From 6a1dbdc371bbdd2a7f5e5645e299104bea93c514 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 13:07:01 -0700 Subject: [PATCH 103/161] Restore a bit of the functionality of vcpkg_setup.bat. --- vcpkg_setup.bat | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/vcpkg_setup.bat b/vcpkg_setup.bat index b190b5bd..3ec82dda 100644 --- a/vcpkg_setup.bat +++ b/vcpkg_setup.bat @@ -1,3 +1,14 @@ @ECHO OFF -ECHO vcpkg_setup.bat is deprecated and no longer necessary. It is recommended to remove it from your build script. +PUSHD submodules\vcpkg + SET VCPKG_ROOT=%CD% + ECHO VCPKG_ROOT = %VCPKG_ROOT% +POPD + +SET VCPKG_EXE=%VCPKG_ROOT%\vcpkg.exe + +IF NOT EXIST "%VCPKG_EXE%" ( + ECHO Building vcpkg... + CALL %VCPKG_ROOT%\bootstrap-vcpkg.bat + IF %ERRORLEVEL% NEQ 0 EXIT /B +) From 0530302d8a9abb89c2a6e0e73c8fb0b3ecab521f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 14:18:55 -0700 Subject: [PATCH 104/161] re-add /MP --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af23680..b36a8c19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,10 @@ if (MSVC) string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob1") -# Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! + # Improve build performance when running without ninja + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + + # Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") From 9059ecbc57cd0fb9be41ba184c928a7b83198cb3 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 14:19:26 -0700 Subject: [PATCH 105/161] Don't have a dependency on discord for static builds --- vcpkg.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vcpkg.json b/vcpkg.json index 840a4d45..e3291723 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -8,7 +8,10 @@ "cpp-httplib", "libzippp", "fmt", - "discord-game-sdk", + { + "name": "discord-game-sdk", + "platform": "!static" + }, "stb", "nlohmann-json", "catch2" From 5396d5f95ee9009c9f206d4c2955ca0fcf5aa960 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 14:52:31 -0700 Subject: [PATCH 106/161] Changes to support static linking --- CMakeLists.txt | 5 +++++ tf2_bot_detector/CMakeLists.txt | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b36a8c19..68c8df25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.17.2) +cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY project(tf2_bot_detector VERSION 1.1.0) message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") @@ -16,6 +17,10 @@ else() add_compile_definitions(TF2BD_IS_CI_COMPILE=0) endif() +if (VCPKG_CRT_LINKAGE MATCHES "static") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE true) if (MSVC) diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index 701ef70f..b91d5d7a 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -197,6 +197,7 @@ find_package(OpenSSL REQUIRED) find_package(nlohmann_json CONFIG REQUIRED) find_package(libzippp CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED) +find_package(BZip2 REQUIRED) target_link_libraries(tf2_bot_detector PRIVATE tf2_bot_detector_common @@ -210,6 +211,7 @@ target_link_libraries(tf2_bot_detector PRIVATE OpenSSL::SSL # cpp-httplib requires openssl nlohmann_json::nlohmann_json fmt::fmt + BZip2::BZip2 ) find_path(HTTPLIB_PATH NAMES httplib.h) From ca032664eaa18b860c3ea4625bc2c2dd06be39b9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 19:02:11 -0700 Subject: [PATCH 107/161] Add the updater as an externalproject so it can be compiled with different linkage. --- CMakeLists.txt | 20 +++++++++++++++- submodules/mh_stuff | 2 +- tf2_bot_detector/CMakeLists.txt | 4 ++-- .../Platform/Windows/PlatformInstall.cpp | 2 ++ tf2_bot_detector_common/CMakeLists.txt | 10 +++++++- tf2_bot_detector_updater/CMakeLists.txt | 24 +++++++++++++++---- tf2_bot_detector_updater/Update_MSIX.cpp | 2 +- tf2_bot_detector_updater/vcpkg.json | 10 ++++++++ 8 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 tf2_bot_detector_updater/vcpkg.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 68c8df25..af311402 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.17.2) cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY project(tf2_bot_detector VERSION 1.1.0) +include(ExternalProject) + message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") # We use this as the build number. @@ -24,6 +26,13 @@ endif() set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE true) if (MSVC) + message("CMAKE_VS_PLATFORM_NAME = ${CMAKE_VS_PLATFORM_NAME}") + message("CMAKE_VS_PLATFORM_NAME_DEFAULT = ${CMAKE_VS_PLATFORM_NAME_DEFAULT}") + message("CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}") + message("CMAKE_GENERATOR_PLATFORM = ${CMAKE_GENERATOR_PLATFORM}") + message("VCPKG_TARGET_TRIPLET = ${VCPKG_TARGET_TRIPLET}") + + # Enable inlining of functions marked "inline" even in debug builds string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob1") @@ -47,7 +56,7 @@ add_subdirectory(submodules/SourceRCON) add_subdirectory(submodules/imgui_desktop) add_subdirectory(submodules/mh_stuff) add_subdirectory(tf2_bot_detector_common) -add_subdirectory(tf2_bot_detector_updater) +# add_subdirectory(tf2_bot_detector_updater) add_subdirectory(tf2_bot_detector) set(ENABLE_EXAMPLES off CACHE BOOL "Build examples" FORCE) @@ -72,3 +81,12 @@ install(DIRECTORY staging/ staging/ DESTINATION "/" FILES_MATCHING PATTERN "staging/logs" EXCLUDE ) install(TARGETS tf2_bot_detector DESTINATION /) + +ExternalProject_Add(tf2_bot_detector_updater + SOURCE_DIR "${PROJECT_SOURCE_DIR}/tf2_bot_detector_updater" + BINARY_DIR "${PROJECT_BINARY_DIR}/tf2_bot_detector_updater" + INSTALL_COMMAND "" + STEP_TARGETS build + # CONFIGURE_COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}-static -DVCPKG_CRT_LINKAGE=static -DVCPKG_LIBRARY_LINKAGE=static -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} "${CMAKE_SOURCE_DIR}/tf2_bot_detector_updater" + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}-static -DVCPKG_CRT_LINKAGE=static -DVCPKG_LIBRARY_LINKAGE=static -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} +) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index cff3a662..ef5a870a 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit cff3a6624abddb9add8bdc7941323ad3eceefe1a +Subproject commit ef5a870ade6ed9fa3d5e9b7bec67d757ce00b9e1 diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index b91d5d7a..dee2a9ad 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -200,9 +200,9 @@ find_package(fmt CONFIG REQUIRED) find_package(BZip2 REQUIRED) target_link_libraries(tf2_bot_detector PRIVATE - tf2_bot_detector_common + tf2_bot_detector::common imgui_desktop - mh_stuff + mh::stuff ValveFileVDF libzip::libzip libzippp::libzippp diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index d66ffb12..26a700e4 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -88,6 +88,7 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat return std::async([bundleUri]() -> InstallUpdate::Result { +#if 0 const Uri uri = mh::format(L"ms-appinstaller:?source={}", ToWC(bundleUri)); DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Attempting to LaunchUriAsync for {}", ToMB(uri.ToString())); @@ -98,6 +99,7 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat return InstallUpdate::StartedNoFeedback{}; } else +#endif { DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "LaunchUriAsync returned false, requesting update tool"); return InstallUpdate::NeedsUpdateTool diff --git a/tf2_bot_detector_common/CMakeLists.txt b/tf2_bot_detector_common/CMakeLists.txt index 09b4fee3..3b9cd569 100644 --- a/tf2_bot_detector_common/CMakeLists.txt +++ b/tf2_bot_detector_common/CMakeLists.txt @@ -1,8 +1,16 @@ +cmake_minimum_required(VERSION 3.17) +project(tf2_bot_detector_common) add_library(tf2_bot_detector_common INTERFACE) +add_library(tf2_bot_detector::common ALIAS tf2_bot_detector_common) target_sources(tf2_bot_detector_common INTERFACE "include/ReleaseChannel.h" ) -target_include_directories(tf2_bot_detector_common INTERFACE include) +target_include_directories(tf2_bot_detector_common INTERFACE "include") + +target_link_libraries(tf2_bot_detector_common + INTERFACE + mh::stuff +) diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index d1e03646..7a6ec7cc 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 3.17) +cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY project(tf2_bot_detector_updater) add_executable(tf2_bot_detector_updater @@ -13,17 +15,31 @@ if (WIN32) endif() set_target_properties(tf2_bot_detector_updater PROPERTIES - CXX_STANDARD 17 + CXX_STANDARD 20 RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ) + message("CMAKE_VS_PLATFORM_NAME = ${CMAKE_VS_PLATFORM_NAME}") + message("CMAKE_VS_PLATFORM_NAME_DEFAULT = ${CMAKE_VS_PLATFORM_NAME_DEFAULT}") + message("CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}") + message("CMAKE_GENERATOR_PLATFORM = ${CMAKE_GENERATOR_PLATFORM}") + message("VCPKG_TARGET_TRIPLET = ${VCPKG_TARGET_TRIPLET}") + message("MSVC_RUNTIME_LIBRARY = ${MSVC_RUNTIME_LIBRARY}") find_package(fmt CONFIG REQUIRED) +# include(../tf2_bot_detector_common/CMakeLists.txt) +add_subdirectory(../submodules/mh_stuff "${PROJECT_BINARY_DIR}/submodules/mh_stuff") + +# add_library(tf2_bot_detector::common INTERFACE IMPORTED) +# find_package(tf2_bot_detector) + +# find_package(tf2_bot_detector REQUIRED) +add_subdirectory(../tf2_bot_detector_common "${PROJECT_BINARY_DIR}/tf2_bot_detector_common") target_link_libraries(tf2_bot_detector_updater PRIVATE - tf2_bot_detector_common - mh_stuff - fmt::fmt-header-only + tf2_bot_detector::common + mh::stuff + fmt::fmt ) diff --git a/tf2_bot_detector_updater/Update_MSIX.cpp b/tf2_bot_detector_updater/Update_MSIX.cpp index c6c8d396..5b88f5b2 100644 --- a/tf2_bot_detector_updater/Update_MSIX.cpp +++ b/tf2_bot_detector_updater/Update_MSIX.cpp @@ -28,7 +28,7 @@ int tf2_bot_detector::Updater::Update_MSIX() try std::cerr << "Attempting to install via API..." << std::endl; const auto mainBundleURL = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", - s_CmdLineArgs.m_ReleaseChannel); + mh::enum_fmt(s_CmdLineArgs.m_ReleaseChannel)); std::wcerr << "Main bundle URL: " << mainBundleURL; const Uri uri = mainBundleURL; diff --git a/tf2_bot_detector_updater/vcpkg.json b/tf2_bot_detector_updater/vcpkg.json new file mode 100644 index 00000000..ab426cc6 --- /dev/null +++ b/tf2_bot_detector_updater/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "tf2-bot-detector", + "version-string": "", + "dependencies": [ + "openssl", + "cpp-httplib", + "libzippp", + "fmt" + ] +} From 44403c6bb0fe7c4c66e7bda579c151da9cc31ff9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 21:26:06 -0700 Subject: [PATCH 108/161] ...whatever, just have CI compile it separately --- .github/workflows/ccpp.yml | 128 ++++++++++++++++++-- CMakeLists.txt | 9 -- tf2_bot_detector_updater/CMakeSettings.json | 100 +++++++++++++++ 3 files changed, 219 insertions(+), 18 deletions(-) create mode 100644 tf2_bot_detector_updater/CMakeSettings.json diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index cd7d03f9..dcc2cc3c 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -134,15 +134,6 @@ jobs: certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - - name: "Artifacts: tf2_bot_detector_updater" - if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD - uses: actions/upload-artifact@v2 - with: - name: "updater_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" - path: | - ${{ steps.tf2bd_paths.outputs.build_dir }}/tf2_bot_detector_updater/*.dll - ${{ steps.tf2bd_paths.outputs.build_dir }}/tf2_bot_detector_updater/*.exe - - name: "Artifacts: Fresh, signed exe" if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 @@ -183,6 +174,125 @@ jobs: + build_updater: + needs: setup_version + runs-on: ${{ matrix.os }} + strategy: + # fail-fast: false + matrix: + os: [windows-latest] + triplet: [x86-windows, x64-windows] + build_type: [Debug, Release] + include: + - os: windows-latest + triplet: x86-windows + tf2bd_arch: x86 + - os: windows-latest + triplet: x64-windows + tf2bd_arch: x64 + + steps: + - name: Retrieve TF2BD_VERSION + uses: nick-invision/persist-action-data@v1 + with: + retrieve_variables: TF2BD_VERSION + - name: Determine artifact behavior + if: matrix.discord_integration == true + run: echo "::set-env name=TF2BD_ENABLE_ARTIFACT_UPLOAD::1" + + - name: Config cross-platform paths + id: tf2bd_paths + shell: bash + run: | + tf2bd_workspace=`realpath "${{ github.workspace }}"` + echo "::set-output name=workspace::$tf2bd_workspace" + echo "::set-output name=build_dir::$tf2bd_workspace/tf2bd_cmake_build_dir/" + + - name: Debug + run: | + echo "github.event = ${{ github.event }}" + echo "github.event_name = ${{ github.event_name }}" + echo "github.sha = ${{ github.sha }}" + echo "github.ref = ${{ github.ref }}" + echo "github.run_id = ${{ github.run_id }}" + echo "github.run_number = ${{ github.run_number }}" + echo "matrix.os = ${{ matrix.os }}" + echo "matrix.triplet = ${{ matrix.triplet }}" + echo "matrix.discord_integration = ${{ matrix.discord_integration }}" + echo "matrix.tf2bd_arch = ${{ matrix.tf2bd_arch }}" + echo "matrix.build_type = ${{ matrix.build_type }}" + echo "env.TF2BD_ENABLE_ARTIFACT_UPLOAD = ${{ env.TF2BD_ENABLE_ARTIFACT_UPLOAD }}" + echo "env.TF2BD_VERSION = ${{ env.TF2BD_VERSION }}" + echo "steps.tf2bd_paths.outputs.workspace = ${{ steps.tf2bd_paths.outputs.workspace }}" + echo "steps.tf2bd_paths.outputs.build_dir = ${{ steps.tf2bd_paths.outputs.build_dir }}" + + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: run-vcpkg + uses: lukka/run-vcpkg@v3.2 + env: + VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' + VCPKG_FEATURE_FLAGS: -manifests + with: + vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' + vcpkgArguments: 'fmt --dry-run' + vcpkgTriplet: ${{ matrix.triplet }} + appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }} + + - name: Configure build tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.tf2bd_arch }} + + - name: CMake + if: ${{ startsWith(matrix.os, 'windows') }} + shell: bash + env: + VCPKG_FEATURE_FLAGS: manifests + run: | + mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" + cd "${{ steps.tf2bd_paths.outputs.build_dir }}" + cmake -G Ninja \ + -DTF2BD_IS_CI_COMPILE=ON \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ + -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ + ../tf2_bot_detector_updater/ + cmake --build . --config ${{ matrix.build_type }} + + - name: Sign artifacts + if: ${{ startsWith(matrix.os, 'windows') }} + uses: PazerOP/code-sign-action@v3 + with: + folder: '${{ steps.tf2bd_paths.outputs.build_dir }}' + recursive: true + certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' + password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + + - name: "Artifacts: tf2_bot_detector_updater" + if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD + uses: actions/upload-artifact@v2 + with: + name: "updater_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + path: | + ${{ steps.tf2bd_paths.outputs.build_dir }}/*.dll + ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe + + - name: "Artifacts: symbols" + if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') + uses: actions/upload-artifact@v2 + with: + name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + path: "${{ steps.tf2bd_paths.outputs.build_dir }}/**.pdb" + + + msix: runs-on: windows-latest needs: build diff --git a/CMakeLists.txt b/CMakeLists.txt index af311402..5cec66b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,12 +81,3 @@ install(DIRECTORY staging/ staging/ DESTINATION "/" FILES_MATCHING PATTERN "staging/logs" EXCLUDE ) install(TARGETS tf2_bot_detector DESTINATION /) - -ExternalProject_Add(tf2_bot_detector_updater - SOURCE_DIR "${PROJECT_SOURCE_DIR}/tf2_bot_detector_updater" - BINARY_DIR "${PROJECT_BINARY_DIR}/tf2_bot_detector_updater" - INSTALL_COMMAND "" - STEP_TARGETS build - # CONFIGURE_COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}-static -DVCPKG_CRT_LINKAGE=static -DVCPKG_LIBRARY_LINKAGE=static -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} "${CMAKE_SOURCE_DIR}/tf2_bot_detector_updater" - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET}-static -DVCPKG_CRT_LINKAGE=static -DVCPKG_LIBRARY_LINKAGE=static -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -) diff --git a/tf2_bot_detector_updater/CMakeSettings.json b/tf2_bot_detector_updater/CMakeSettings.json new file mode 100644 index 00000000..92c74b3f --- /dev/null +++ b/tf2_bot_detector_updater/CMakeSettings.json @@ -0,0 +1,100 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\..\\out\\build\\${name}", + "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "TF2BD_ENABLE_TESTS", + "value": "True", + "type": "BOOL" + }, + { + "name": "VCPKG_TARGET_TRIPLET", + "value": "x64-windows-static", + "type": "STRING" + } + ], + "cmakeToolchain": "../submodules/vcpkg/scripts/buildsystems/vcpkg.cmake" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\..\\out\\build\\${name}", + "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "TF2BD_ENABLE_TESTS", + "value": "True", + "type": "BOOL" + }, + { + "name": "VCPKG_TARGET_TRIPLET", + "value": "x64-windows-static", + "type": "STRING" + } + ], + "cmakeToolchain": "../submodules/vcpkg/scripts/buildsystems/vcpkg.cmake" + }, + { + "name": "x86-Release", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\..\\out\\build\\${name}", + "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86_x64" ], + "variables": [ + { + "name": "TF2BD_ENABLE_TESTS", + "value": "True", + "type": "BOOL" + }, + { + "name": "VCPKG_TARGET_TRIPLET", + "value": "x86-windows-static", + "type": "STRING" + } + ], + "cmakeToolchain": "../submodules/vcpkg/scripts/buildsystems/vcpkg.cmake" + }, + { + "name": "x86-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\..\\out\\build\\${name}", + "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x86_x64" ], + "variables": [ + { + "name": "TF2BD_ENABLE_TESTS", + "value": "True", + "type": "BOOL" + }, + { + "name": "VCPKG_TARGET_TRIPLET", + "value": "x86-windows-static", + "type": "STRING" + } + ], + "cmakeToolchain": "../submodules/vcpkg/scripts/buildsystems/vcpkg.cmake" + } + ] +} \ No newline at end of file From 6972ef02e9e8e831d7db7756f1fff8322386240c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 21:33:18 -0700 Subject: [PATCH 109/161] fix conflicting cache keys --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index dcc2cc3c..5272eabf 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -99,7 +99,7 @@ jobs: vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' vcpkgArguments: 'fmt --dry-run' vcpkgTriplet: ${{ matrix.triplet }} - appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }} + appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 @@ -174,7 +174,7 @@ jobs: - build_updater: + updater: needs: setup_version runs-on: ${{ matrix.os }} strategy: @@ -240,7 +240,7 @@ jobs: vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' vcpkgArguments: 'fmt --dry-run' vcpkgTriplet: ${{ matrix.triplet }} - appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }} + appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 From c4494d047da103e78769f6d23ad91967a0456621 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 21:42:07 -0700 Subject: [PATCH 110/161] Fix triplet for updater --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 5272eabf..90762320 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -181,7 +181,7 @@ jobs: # fail-fast: false matrix: os: [windows-latest] - triplet: [x86-windows, x64-windows] + triplet: [x86-windows-static, x64-windows-static] build_type: [Debug, Release] include: - os: windows-latest From 56490036715b01ce61c89a1d61ea0e1f10f0000f Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 21:51:46 -0700 Subject: [PATCH 111/161] Always upload updater artifacts --- .github/workflows/ccpp.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 90762320..4815d96d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -196,9 +196,6 @@ jobs: uses: nick-invision/persist-action-data@v1 with: retrieve_variables: TF2BD_VERSION - - name: Determine artifact behavior - if: matrix.discord_integration == true - run: echo "::set-env name=TF2BD_ENABLE_ARTIFACT_UPLOAD::1" - name: Config cross-platform paths id: tf2bd_paths @@ -221,7 +218,6 @@ jobs: echo "matrix.discord_integration = ${{ matrix.discord_integration }}" echo "matrix.tf2bd_arch = ${{ matrix.tf2bd_arch }}" echo "matrix.build_type = ${{ matrix.build_type }}" - echo "env.TF2BD_ENABLE_ARTIFACT_UPLOAD = ${{ env.TF2BD_ENABLE_ARTIFACT_UPLOAD }}" echo "env.TF2BD_VERSION = ${{ env.TF2BD_VERSION }}" echo "steps.tf2bd_paths.outputs.workspace = ${{ steps.tf2bd_paths.outputs.workspace }}" echo "steps.tf2bd_paths.outputs.build_dir = ${{ steps.tf2bd_paths.outputs.build_dir }}" @@ -276,7 +272,6 @@ jobs: password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - name: "Artifacts: tf2_bot_detector_updater" - if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD uses: actions/upload-artifact@v2 with: name: "updater_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" @@ -285,7 +280,7 @@ jobs: ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe - name: "Artifacts: symbols" - if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') + if: startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" From c7dda9ee120da463a179ff86070e54ced2f9865e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 21:54:49 -0700 Subject: [PATCH 112/161] Fix updater triplet, again --- .github/workflows/ccpp.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 4815d96d..dcc4de01 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -178,17 +178,16 @@ jobs: needs: setup_version runs-on: ${{ matrix.os }} strategy: - # fail-fast: false matrix: os: [windows-latest] triplet: [x86-windows-static, x64-windows-static] build_type: [Debug, Release] include: - os: windows-latest - triplet: x86-windows + triplet: x86-windows-static tf2bd_arch: x86 - os: windows-latest - triplet: x64-windows + triplet: x64-windows-static tf2bd_arch: x64 steps: From 890e506c2dbbc8073108d68b5ce9c8109f57fff9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 21:59:30 -0700 Subject: [PATCH 113/161] Don't sign binaries recursively --- .github/workflows/ccpp.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index dcc4de01..ab30e60c 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -130,7 +130,6 @@ jobs: uses: PazerOP/code-sign-action@v3 with: folder: '${{ steps.tf2bd_paths.outputs.build_dir }}' - recursive: true certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' @@ -266,7 +265,6 @@ jobs: uses: PazerOP/code-sign-action@v3 with: folder: '${{ steps.tf2bd_paths.outputs.build_dir }}' - recursive: true certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' From dac23e6391f962e244eaa0bb285667b775d42f92 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 22:09:19 -0700 Subject: [PATCH 114/161] prevent the two projects from stepping on each other --- .github/workflows/ccpp.yml | 6 ++++++ CMakeSettings.json | 20 ++++++++++---------- tf2_bot_detector_updater/CMakeLists.txt | 6 +++--- tf2_bot_detector_updater/CMakeSettings.json | 20 ++++++++++---------- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index ab30e60c..f6b1059c 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -138,6 +138,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "smartscreen_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + if-no-files-found: error path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" @@ -169,6 +170,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + if-no-files-found: error path: "${{ steps.tf2bd_paths.outputs.build_dir }}/**.pdb" @@ -272,6 +274,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: "updater_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + if-no-files-found: error path: | ${{ steps.tf2bd_paths.outputs.build_dir }}/*.dll ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe @@ -280,6 +283,7 @@ jobs: if: startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: + if-no-files-found: error name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" path: "${{ steps.tf2bd_paths.outputs.build_dir }}/**.pdb" @@ -355,6 +359,7 @@ jobs: - name: Upload msix package uses: actions/upload-artifact@v2 with: + if-no-files-found: error name: ${{ env.TF2BD_MSIX_FILENAME }} path: "msix/${{ env.TF2BD_MSIX_FILENAME }}" @@ -410,5 +415,6 @@ jobs: - name: Upload bundle (github actions artifact) uses: actions/upload-artifact@v2 with: + if-no-files-found: error name: ${{ env.TF2BD_MSIXBUNDLE_NAME }} path: "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" diff --git a/CMakeSettings.json b/CMakeSettings.json index e2105d67..db7806c7 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -1,12 +1,12 @@ -{ +{ "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\out\\build_tf2_bot_detector\\${name}", + "installRoot": "${projectDir}\\out\\install_tf2_bot_detector\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -23,8 +23,8 @@ "name": "x64-Release", "generator": "Ninja", "configurationType": "Release", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\out\\build_tf2_bot_detector\\${name}", + "installRoot": "${projectDir}\\out\\install_tf2_bot_detector\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -42,8 +42,8 @@ "name": "x86-Release", "generator": "Ninja", "configurationType": "Release", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\out\\build_tf2_bot_detector\\${name}", + "installRoot": "${projectDir}\\out\\install_tf2_bot_detector\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -61,8 +61,8 @@ "name": "x86-Debug", "generator": "Ninja", "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", + "buildRoot": "${projectDir}\\out\\build_tf2_bot_detector\\${name}", + "installRoot": "${projectDir}\\out\\install_tf2_bot_detector\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -77,4 +77,4 @@ "cmakeToolchain": "submodules/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] -} \ No newline at end of file +} diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index 7a6ec7cc..a0f2add5 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -16,9 +16,9 @@ endif() set_target_properties(tf2_bot_detector_updater PROPERTIES CXX_STANDARD 20 - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" - RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" + # RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" + # RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" + # RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}" MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ) message("CMAKE_VS_PLATFORM_NAME = ${CMAKE_VS_PLATFORM_NAME}") diff --git a/tf2_bot_detector_updater/CMakeSettings.json b/tf2_bot_detector_updater/CMakeSettings.json index 92c74b3f..6115c657 100644 --- a/tf2_bot_detector_updater/CMakeSettings.json +++ b/tf2_bot_detector_updater/CMakeSettings.json @@ -1,12 +1,12 @@ -{ +{ "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x64_x64" ], - "buildRoot": "${projectDir}\\..\\out\\build\\${name}", - "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "buildRoot": "${projectDir}\\..\\out\\build_updater\\${name}", + "installRoot": "${projectDir}\\..\\out\\install_updater\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -28,8 +28,8 @@ "name": "x64-Release", "generator": "Ninja", "configurationType": "Release", - "buildRoot": "${projectDir}\\..\\out\\build\\${name}", - "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "buildRoot": "${projectDir}\\..\\out\\build_updater\\${name}", + "installRoot": "${projectDir}\\..\\out\\install_updater\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -52,8 +52,8 @@ "name": "x86-Release", "generator": "Ninja", "configurationType": "Release", - "buildRoot": "${projectDir}\\..\\out\\build\\${name}", - "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "buildRoot": "${projectDir}\\..\\out\\build_updater\\${name}", + "installRoot": "${projectDir}\\..\\out\\install_updater\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -76,8 +76,8 @@ "name": "x86-Debug", "generator": "Ninja", "configurationType": "Debug", - "buildRoot": "${projectDir}\\..\\out\\build\\${name}", - "installRoot": "${projectDir}\\..\\out\\install\\${name}", + "buildRoot": "${projectDir}\\..\\out\\build_updater\\${name}", + "installRoot": "${projectDir}\\..\\out\\install_updater\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", @@ -97,4 +97,4 @@ "cmakeToolchain": "../submodules/vcpkg/scripts/buildsystems/vcpkg.cmake" } ] -} \ No newline at end of file +} From 58345714b1bc10cd7c697cd8b1a856a7a12a9d5d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 22:30:32 -0700 Subject: [PATCH 115/161] Move common cmake init code into its own files --- CMakeLists.txt | 67 ++----------------------- cmake/init-postproject.cmake | 46 +++++++++++++++++ cmake/init-preproject.cmake | 1 + tf2_bot_detector_updater/CMakeLists.txt | 6 ++- 4 files changed, 55 insertions(+), 65 deletions(-) create mode 100644 cmake/init-postproject.cmake create mode 100644 cmake/init-preproject.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cec66b3..58b2188b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,55 +1,8 @@ -cmake_minimum_required(VERSION 3.17.2) -cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY -project(tf2_bot_detector VERSION 1.1.0) +cmake_minimum_required(VERSION 3.17) -include(ExternalProject) - -message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") - -# We use this as the build number. -message("TF2BD CMAKE_PROJECT_VERSION_TWEAK = ${CMAKE_PROJECT_VERSION_TWEAK}") -if ("${CMAKE_PROJECT_VERSION_TWEAK}" STREQUAL "") - set(CMAKE_PROJECT_VERSION_TWEAK 0) -endif() - -option(TF2BD_IS_CI_COMPILE "Set to true if this is a compile on a CI service. Used to help determine if user has made modifications to the source code." off) -if (TF2BD_IS_CI_COMPILE) - add_compile_definitions(TF2BD_IS_CI_COMPILE=1) -else() - add_compile_definitions(TF2BD_IS_CI_COMPILE=0) -endif() - -if (VCPKG_CRT_LINKAGE MATCHES "static") - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -endif() - -set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE true) - -if (MSVC) - message("CMAKE_VS_PLATFORM_NAME = ${CMAKE_VS_PLATFORM_NAME}") - message("CMAKE_VS_PLATFORM_NAME_DEFAULT = ${CMAKE_VS_PLATFORM_NAME_DEFAULT}") - message("CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}") - message("CMAKE_GENERATOR_PLATFORM = ${CMAKE_GENERATOR_PLATFORM}") - message("VCPKG_TARGET_TRIPLET = ${VCPKG_TARGET_TRIPLET}") - - # Enable inlining of functions marked "inline" even in debug builds - string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob1") - - # Improve build performance when running without ninja - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - - # Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") - - if (CMAKE_BUILD_TYPE MATCHES "Release") - add_link_options(/OPT:REF /OPT:ICF) - endif() - - add_definitions(/await) -endif() +include(cmake/init-preproject.cmake) + project(tf2_bot_detector VERSION 1.1.0) +include(cmake/init-postproject.cmake) add_subdirectory(submodules/ValveFileVDF) add_subdirectory(submodules/SourceRCON) @@ -69,15 +22,3 @@ option(TF2BD_ENABLE_TESTS "Enable test compilation" off) if(MSVC) target_compile_options(tf2_bot_detector PRIVATE /WX) endif() - -# "installation" aka create a build we can upload to github as a release -if (WIN32) - file(GLOB TF2BD_INSTALL_DEPS_DLL LIST_DIRECTORIES false "${CMAKE_BINARY_DIR}/*.dll") - install(FILES ${TF2BD_INSTALL_DEPS_DLL} DESTINATION /) -endif() -install(DIRECTORY staging/ staging/ DESTINATION "/" FILES_MATCHING - PATTERN "*" - PATTERN "staging/cfg/settings.json" EXCLUDE - PATTERN "staging/logs" EXCLUDE -) -install(TARGETS tf2_bot_detector DESTINATION /) diff --git a/cmake/init-postproject.cmake b/cmake/init-postproject.cmake new file mode 100644 index 00000000..2f0df48d --- /dev/null +++ b/cmake/init-postproject.cmake @@ -0,0 +1,46 @@ +message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") + +# We use this as the build number. +message("TF2BD CMAKE_PROJECT_VERSION_TWEAK = ${CMAKE_PROJECT_VERSION_TWEAK}") +if ("${CMAKE_PROJECT_VERSION_TWEAK}" STREQUAL "") + set(CMAKE_PROJECT_VERSION_TWEAK 0) +endif() + +option(TF2BD_IS_CI_COMPILE "Set to true if this is a compile on a CI service. Used to help determine if user has made modifications to the source code." off) +if (TF2BD_IS_CI_COMPILE) + add_compile_definitions(TF2BD_IS_CI_COMPILE=1) +else() + add_compile_definitions(TF2BD_IS_CI_COMPILE=0) +endif() + +if (VCPKG_CRT_LINKAGE MATCHES "static") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +endif() + +set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION_RELEASE true) + +if (MSVC) + message("CMAKE_VS_PLATFORM_NAME = ${CMAKE_VS_PLATFORM_NAME}") + message("CMAKE_VS_PLATFORM_NAME_DEFAULT = ${CMAKE_VS_PLATFORM_NAME_DEFAULT}") + message("CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}") + message("CMAKE_GENERATOR_PLATFORM = ${CMAKE_GENERATOR_PLATFORM}") + message("VCPKG_TARGET_TRIPLET = ${VCPKG_TARGET_TRIPLET}") + + # Enable inlining of functions marked "inline" even in debug builds + string(REPLACE "/Ob0" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob1") + + # Improve build performance when running without ninja + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + + # Generate PDBs for release builds - RelWithDebInfo is NOT a Release build! + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG") + + if (CMAKE_BUILD_TYPE MATCHES "Release") + add_link_options(/OPT:REF /OPT:ICF) + endif() + + add_definitions(/await) +endif() diff --git a/cmake/init-preproject.cmake b/cmake/init-preproject.cmake new file mode 100644 index 00000000..b42f0b0f --- /dev/null +++ b/cmake/init-preproject.cmake @@ -0,0 +1 @@ +cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index a0f2add5..9f5d21d4 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.17) -cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY -project(tf2_bot_detector_updater) + +include(../cmake/init-preproject.cmake) + project(tf2_bot_detector_updater) +include(../cmake/init-postproject.cmake) add_executable(tf2_bot_detector_updater "main.cpp" From 91accca2cc658b2195ee813fb30c19444db5c044 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 22:48:30 -0700 Subject: [PATCH 116/161] Try manually caching vcpkg --- .github/workflows/ccpp.yml | 52 ++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f6b1059c..3008315e 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -90,16 +90,26 @@ jobs: with: submodules: recursive - - name: run-vcpkg - uses: lukka/run-vcpkg@v3.2 + - name: cache-vcpkg + uses: actions/cache@v2 env: - VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' - VCPKG_FEATURE_FLAGS: -manifests + VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - vcpkgArguments: 'fmt --dry-run' - vcpkgTriplet: ${{ matrix.triplet }} - appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} + key: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ matrix.triplet }} + path: | + ${{ env.VCPKG_ROOT }}/archives + ${{ env.VCPKG_ROOT }}/vcpkg.exe + + # - name: run-vcpkg + # uses: lukka/run-vcpkg@v3.2 + # env: + # VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' + # VCPKG_FEATURE_FLAGS: -manifests + # with: + # vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' + # vcpkgArguments: 'fmt --dry-run' + # vcpkgTriplet: ${{ matrix.triplet }} + # appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 @@ -227,16 +237,26 @@ jobs: with: submodules: recursive - - name: run-vcpkg - uses: lukka/run-vcpkg@v3.2 + - name: cache-vcpkg + uses: actions/cache@v2 env: - VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' - VCPKG_FEATURE_FLAGS: -manifests + VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - vcpkgArguments: 'fmt --dry-run' - vcpkgTriplet: ${{ matrix.triplet }} - appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} + key: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ matrix.triplet }} + path: | + ${{ env.VCPKG_ROOT }}/archives + ${{ env.VCPKG_ROOT }}/vcpkg.exe + + # - name: run-vcpkg + # uses: lukka/run-vcpkg@v3.2 + # env: + # VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' + # VCPKG_FEATURE_FLAGS: -manifests + # with: + # vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' + # vcpkgArguments: 'fmt --dry-run' + # vcpkgTriplet: ${{ matrix.triplet }} + # appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 From d0e9addbadb109f1addeac928d46db51caa9f657 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 22:58:58 -0700 Subject: [PATCH 117/161] Init vcpkg if missing --- .github/workflows/ccpp.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 3008315e..c9fb0ece 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -99,6 +99,12 @@ jobs: path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe + - uses: lukka/run-vcpkg@v3.3 + with: + vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' + setupOnly: true + doNotCache: true + # - name: run-vcpkg # uses: lukka/run-vcpkg@v3.2 @@ -246,6 +252,11 @@ jobs: path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe + - uses: lukka/run-vcpkg@v3.3 + with: + vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' + setupOnly: true + doNotCache: true # - name: run-vcpkg # uses: lukka/run-vcpkg@v3.2 From 261fa6c4697ae0df3c757a9a684eaeb5c36f948c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 31 Aug 2020 23:12:05 -0700 Subject: [PATCH 118/161] fix cache key generation --- .github/workflows/ccpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index c9fb0ece..8846dda6 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -95,7 +95,7 @@ jobs: env: VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - key: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ matrix.triplet }} + key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('vcpkg.json') }}-${{ matrix.triplet }} path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe @@ -248,7 +248,7 @@ jobs: env: VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - key: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ matrix.triplet }} + key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('tf2_bot_detector_updater/vcpkg.json') }}-${{ matrix.triplet }} path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe From 7331c606c53b31f450648c12e10ce137849ef095 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 10:55:30 -0700 Subject: [PATCH 119/161] cleared up artifact names --- .github/workflows/ccpp.yml | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8846dda6..1b864153 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -105,18 +105,6 @@ jobs: setupOnly: true doNotCache: true - - # - name: run-vcpkg - # uses: lukka/run-vcpkg@v3.2 - # env: - # VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' - # VCPKG_FEATURE_FLAGS: -manifests - # with: - # vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - # vcpkgArguments: 'fmt --dry-run' - # vcpkgTriplet: ${{ matrix.triplet }} - # appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} - - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 with: @@ -185,7 +173,7 @@ jobs: if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 with: - name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + name: "tf2-bot-detector-symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" if-no-files-found: error path: "${{ steps.tf2bd_paths.outputs.build_dir }}/**.pdb" @@ -258,17 +246,6 @@ jobs: setupOnly: true doNotCache: true - # - name: run-vcpkg - # uses: lukka/run-vcpkg@v3.2 - # env: - # VCPKG_JSON_FILE: '${{ github.workspace }}/vcpkg.json' - # VCPKG_FEATURE_FLAGS: -manifests - # with: - # vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - # vcpkgArguments: 'fmt --dry-run' - # vcpkgTriplet: ${{ matrix.triplet }} - # appendedCacheKey: ${{ hashFiles(env.VCPKG_JSON_FILE) }}-${{ github.workflow }}-${{ github.job_id }} - - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 with: @@ -315,7 +292,7 @@ jobs: uses: actions/upload-artifact@v2 with: if-no-files-found: error - name: "symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" + name: "updater-symbols_${{ matrix.triplet }}_${{ env.TF2BD_VERSION }}_${{ matrix.build_type }}" path: "${{ steps.tf2bd_paths.outputs.build_dir }}/**.pdb" From e2d5f22e610be52307ea4ef9badca9d4f4b3f642 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 11:08:44 -0700 Subject: [PATCH 120/161] Replace lukka/run-vcpkg (which was unnecessarily recompiling vcpkg) with seanmiddleditch/gha-setup-ninja. --- .github/workflows/ccpp.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 1b864153..ef673491 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -99,12 +99,8 @@ jobs: path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe - - uses: lukka/run-vcpkg@v3.3 - with: - vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - setupOnly: true - doNotCache: true + - uses: seanmiddleditch/gha-setup-ninja@v2 - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 with: @@ -240,12 +236,8 @@ jobs: path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe - - uses: lukka/run-vcpkg@v3.3 - with: - vcpkgDirectory: '${{ github.workspace }}/submodules/vcpkg' - setupOnly: true - doNotCache: true + - uses: seanmiddleditch/gha-setup-ninja@v2 - name: Configure build tools uses: ilammy/msvc-dev-cmd@v1 with: From 859f51e5f1549e2877a0acf11cd68d4e35bd3f01 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 11:18:03 -0700 Subject: [PATCH 121/161] use native shell for cmake command --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index ef673491..c1ab1d7a 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -245,7 +245,7 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} - shell: bash + # shell: bash env: VCPKG_FEATURE_FLAGS: manifests run: | From bee006fdfe76ccb42a69f62de4cdb6265ca848d8 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 11:22:28 -0700 Subject: [PATCH 122/161] oops, wrong one --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index c1ab1d7a..8cb914c6 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -108,7 +108,7 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} - shell: bash + # shell: bash env: VCPKG_FEATURE_FLAGS: manifests run: | From 98c73133171254831c97973b80f38d48cf753358 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 11:30:34 -0700 Subject: [PATCH 123/161] Remove newlines from cmake command line --- .github/workflows/ccpp.yml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8cb914c6..fdd70ac2 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -114,15 +114,7 @@ jobs: run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -G Ninja \ - -DTF2BD_IS_CI_COMPILE=ON \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ - -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ - -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ - -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ - ../ + cmake -G Ninja -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts @@ -251,15 +243,7 @@ jobs: run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -G Ninja \ - -DTF2BD_IS_CI_COMPILE=ON \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ - -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ - -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ - -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ - -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ - ../tf2_bot_detector_updater/ + cmake -G Ninja -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts From 48b6833bf56785c206731f6a6984afa672bd88be Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 11:35:30 -0700 Subject: [PATCH 124/161] Fix the RUNVCPKG_VCPKG_ROOT environment variable not being set --- .github/workflows/ccpp.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index fdd70ac2..eec8ed4d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -108,13 +108,22 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} - # shell: bash + shell: bash env: VCPKG_FEATURE_FLAGS: manifests + RUNVCPKG_VCPKG_ROOT: ${{ github.workspace }}/submodules/vcpkg run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -G Ninja -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ + cmake -G Ninja \ + -DTF2BD_IS_CI_COMPILE=ON \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ + -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ + ../ cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts @@ -237,13 +246,22 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} - # shell: bash + shell: bash env: VCPKG_FEATURE_FLAGS: manifests + RUNVCPKG_VCPKG_ROOT: ${{ github.workspace }}/submodules/vcpkg run: | mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - cmake -G Ninja -DTF2BD_IS_CI_COMPILE=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} ../ + cmake -G Ninja \ + -DTF2BD_IS_CI_COMPILE=ON \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ + -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ + -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ + ../ cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts From 8188e37c6aef4ea477fa2ad54496b59de851d654 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 1 Sep 2020 11:45:29 -0700 Subject: [PATCH 125/161] Fix compile path for the updater --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index eec8ed4d..b60afed8 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -261,7 +261,7 @@ jobs: -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ - ../ + ../tf2_bot_detector_updater/ cmake --build . --config ${{ matrix.build_type }} - name: Sign artifacts From 4f1f98f8478dee279d4117daf79e5375b91e80ea Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 4 Sep 2020 23:02:37 -0700 Subject: [PATCH 126/161] Forgot to force static linkage for the updater --- .github/workflows/ccpp.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index b60afed8..f31dde8f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -255,6 +255,9 @@ jobs: cd "${{ steps.tf2bd_paths.outputs.build_dir }}" cmake -G Ninja \ -DTF2BD_IS_CI_COMPILE=ON \ + -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ + -DVCPKG_CRT_LINKAGE=static \ + -DVCPKG_LIBRARY_LINKAGE=static \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ From b51092045a50fe07032d4d197805fadf4e8f14e8 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Fri, 4 Sep 2020 23:03:02 -0700 Subject: [PATCH 127/161] Fix timeout for the tool's httpclient being too short --- tf2_bot_detector/Networking/HTTPClient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tf2_bot_detector/Networking/HTTPClient.cpp b/tf2_bot_detector/Networking/HTTPClient.cpp index 157438eb..b8410082 100644 --- a/tf2_bot_detector/Networking/HTTPClient.cpp +++ b/tf2_bot_detector/Networking/HTTPClient.cpp @@ -14,6 +14,7 @@ std::string HTTPClient::GetString(const URL& url) const { httplib::SSLClient client(url.m_Host, url.m_Port); client.set_follow_location(true); + client.set_read_timeout(10); httplib::Headers headers = { From 76764c4e2a7be08968ce837f1dfef52e7dff66c4 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sat, 5 Sep 2020 23:52:19 -0700 Subject: [PATCH 128/161] WIP portable mode self-updating --- submodules/mh_stuff | 2 +- tf2_bot_detector/Log.h | 2 +- tf2_bot_detector/Networking/HTTPClient.cpp | 7 +- tf2_bot_detector/Platform/Platform.h | 3 + .../Platform/Windows/Processes.cpp | 44 ++++ .../SetupFlow/UpdateCheckPage.cpp | 6 +- tf2_bot_detector/UpdateManager.cpp | 236 ++++++++++++++++-- tf2_bot_detector/UpdateManager.h | 4 +- tf2_bot_detector_updater/vcpkg.json | 3 - 9 files changed, 280 insertions(+), 27 deletions(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index ef5a870a..a916f3c5 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit ef5a870ade6ed9fa3d5e9b7bec67d757ce00b9e1 +Subproject commit a916f3c555e74c74f506d706324724cc3063cb4d diff --git a/tf2_bot_detector/Log.h b/tf2_bot_detector/Log.h index cbb27d26..1302f6d9 100644 --- a/tf2_bot_detector/Log.h +++ b/tf2_bot_detector/Log.h @@ -136,7 +136,7 @@ namespace tf2_bot_detector } \ \ template \ - NOINLINE inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ + inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ { \ detail::log_h::LogImpl(color, (severity), (visibility), location, fmtStr, args...); \ } \ diff --git a/tf2_bot_detector/Networking/HTTPClient.cpp b/tf2_bot_detector/Networking/HTTPClient.cpp index b8410082..e365c972 100644 --- a/tf2_bot_detector/Networking/HTTPClient.cpp +++ b/tf2_bot_detector/Networking/HTTPClient.cpp @@ -10,7 +10,7 @@ using namespace std::string_literals; using namespace tf2_bot_detector; -std::string HTTPClient::GetString(const URL& url) const +std::string HTTPClient::GetString(const URL& url) const try { httplib::SSLClient client(url.m_Host, url.m_Port); client.set_follow_location(true); @@ -30,3 +30,8 @@ std::string HTTPClient::GetString(const URL& url) const return response->body; } +catch (const std::exception& e) +{ + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "{}", url); + throw; +} diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index 70fae2cc..d9466729 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -68,6 +68,9 @@ namespace tf2_bot_detector std::shared_future> GetTF2CommandLineArgsAsync(); bool IsSteamRunning(); void RequireTF2NotRunning(); + + void Launch(const std::filesystem::path& executable, const std::vector& args = {}); + int GetCurrentProcessID(); } namespace Shell diff --git a/tf2_bot_detector/Platform/Windows/Processes.cpp b/tf2_bot_detector/Platform/Windows/Processes.cpp index 233ea878..b3b03855 100644 --- a/tf2_bot_detector/Platform/Windows/Processes.cpp +++ b/tf2_bot_detector/Platform/Windows/Processes.cpp @@ -2,6 +2,7 @@ #include "Util/TextUtils.h" #include "Log.h" +#include #include #include @@ -255,3 +256,46 @@ void tf2_bot_detector::Processes::RequireTF2NotRunning() std::exit(1); } } + +void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable, const std::vector& args) +{ + std::wstring cmdLine; + + cmdLine << executable << L' '; + + for (const auto& arg : args) + cmdLine << std::quoted(mh::change_encoding(arg)) << L' '; + + STARTUPINFOW startupInfo{}; + startupInfo.cb = sizeof(startupInfo); + PROCESS_INFORMATION processInfo{}; + + const auto result = CreateProcessW( + nullptr, //mh::format(L"{}", executable).c_str(), + cmdLine.data(), + nullptr, + nullptr, + FALSE, + 0, + nullptr, + nullptr, + &startupInfo, + &processInfo); + + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + + if (result == 0) + { + const auto err = GetLastError(); + auto exception = GetLastErrorException(E_FAIL, err, + mh::format("CreateProcessW() returned {}", MH_SOURCE_LOCATION_CURRENT(), result)); + LogException(MH_SOURCE_LOCATION_CURRENT(), exception); + throw exception; + } +} + +int tf2_bot_detector::Processes::GetCurrentProcessID() +{ + return ::GetCurrentProcessId(); +} diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index 86c1f2a4..e86f3a39 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -123,7 +123,7 @@ namespace case UpdateStatus::UpdateAvailable: { ImGui::TextFmt({ 0, 1, 1, 1 }, "Update available: v{} {:v} (current version v{})", - update->m_State.m_Version, mh::enum_fmt(update->m_State.m_ReleaseChannel), VERSION); + update->m_BuildInfo.m_Version, mh::enum_fmt(update->m_BuildInfo.m_ReleaseChannel), VERSION); ImGui::NewLine(); @@ -142,10 +142,10 @@ namespace ImGui::SameLine(); - ImGui::EnabledSwitch(!update->m_State.m_GitHubURL.empty(), [&] + ImGui::EnabledSwitch(!update->m_BuildInfo.m_GitHubURL.empty(), [&] { if (ImGui::Button("View on GitHub")) - Shell::OpenURL(update->m_State.m_GitHubURL); + Shell::OpenURL(update->m_BuildInfo.m_GitHubURL); }, "Unable to determine GitHub URL"); diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 10606e37..8660efd0 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -8,14 +8,18 @@ #include "Log.h" #include "ReleaseChannel.h" +#include #include #include +#include #include #include +#include #include #include #include +#include #include using namespace std::string_view_literals; @@ -84,13 +88,20 @@ namespace { class UpdateManager final : public IUpdateManager { + struct UpdateToolResult; + struct AvailableUpdate final : IAvailableUpdate { - AvailableUpdate(UpdateManager& parent, BuildInfo&& buildInfo); + AvailableUpdate(const HTTPClient& client, UpdateManager& parent, BuildInfo&& buildInfo); bool CanSelfUpdate() const override; void BeginSelfUpdate() const override; + std::future RunPortableUpdate() const; + + const BuildInfo::BuildVariant* m_Updater = nullptr; + const BuildInfo::BuildVariant* m_Portable = nullptr; + std::shared_ptr m_HTTPClient; UpdateManager& m_Parent; }; @@ -225,8 +236,17 @@ namespace { try { - if (mh::is_future_ready(*future)) - m_UpdateCheckState.emplace(*this, future->get()); + auto client = m_Settings.GetHTTPClient(); + if (client) + { + if (mh::is_future_ready(*future)) + m_UpdateCheckState.emplace(*client, *this, future->get()); + } + else + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable, cancelling update"); + m_UpdateCheckState.emplace<0>(); + } } catch (const std::exception& e) { @@ -274,6 +294,7 @@ namespace default: LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown InstallUpdate::Result index {}", result.index()); + [[fallthrough]]; case mh::variant_type_index_v: return UpdateStatus::Updating; case mh::variant_type_index_v: @@ -314,7 +335,7 @@ namespace case mh::variant_type_index_v: { auto& update = std::get(m_UpdateCheckState); - return update.m_State.m_Version > VERSION ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; + return update.m_BuildInfo.m_Version > VERSION ? UpdateStatus::UpdateAvailable : UpdateStatus::UpToDate; } } @@ -367,26 +388,60 @@ namespace return false; } - UpdateManager::AvailableUpdate::AvailableUpdate(UpdateManager& parent, BuildInfo&& buildInfo) : + static const BuildInfo::BuildVariant* FindNativeVariant(const std::vector& variants) + { + const auto os = Platform::GetOS(); + const auto arch = Platform::GetArch(); + + for (const auto& variant : variants) + { + if (variant.m_OS != os) + continue; + if (variant.m_Arch != arch) + continue; + + return &variant; + } + + return nullptr; + } + + UpdateManager::AvailableUpdate::AvailableUpdate(const HTTPClient& client, UpdateManager& parent, BuildInfo&& buildInfo) : IAvailableUpdate(std::move(buildInfo)), + m_HTTPClient(client.shared_from_this()), m_Parent(parent) { + m_Updater = FindNativeVariant(m_BuildInfo.m_Updater); + m_Portable = FindNativeVariant(m_BuildInfo.m_Portable); } bool UpdateManager::AvailableUpdate::CanSelfUpdate() const { - if (!m_Parent.m_Settings.GetHTTPClient()) - return false; - if (Platform::IsInstalled()) - return Platform::CanInstallUpdate(m_State); - - // If we're portable, we just need to pass some arguments - static const bool s_WarnOnce = [] { - LogWarning(MH_SOURCE_LOCATION_CURRENT(), "TODO: portable self-update not yet implemented"); - return false; - }(); + // Installed + return Platform::CanInstallUpdate(m_BuildInfo); + } + else + { + // Portable mode + if (!m_Updater) + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "Updater not found for build {}, os {}, platform {}.", + m_BuildInfo.m_Version, mh::enum_fmt(Platform::GetOS()), mh::enum_fmt(Platform::GetArch())); + return false; + } + + if (!m_Portable) + { + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "Portable build not found for v{}, os {}, platform {}.", + m_BuildInfo.m_Version, mh::enum_fmt(Platform::GetOS()), mh::enum_fmt(Platform::GetArch())); + return false; + } + + // We should be good to go + return true; + } return false; } @@ -406,7 +461,156 @@ namespace std::terminate(); } - m_Parent.m_State = Platform::BeginInstallUpdate(m_State, *client); + if (Platform::IsInstalled()) + { + if (Platform::CanInstallUpdate(m_BuildInfo)) + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed and able to install update"); + m_Parent.m_State = Platform::BeginInstallUpdate(m_BuildInfo, *client); + } + else + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed but cannot install update"); + } + } + else + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports *not* being installed. Running portable update."); + m_Parent.m_State = RunPortableUpdate(); + } + } + + static void ExtractArchive(const libzippp::ZipArchive& archive, const std::filesystem::path& directory) + { + try + { + std::filesystem::create_directories(directory); + } + catch (...) + { + std::throw_with_nested(std::runtime_error(mh::format("{}: Failed to create directory(s) for {}", + MH_SOURCE_LOCATION_CURRENT(), directory))); + } + + try + { + for (const auto& entry : archive.getEntries()) + { + const std::filesystem::path path = directory / entry.getName(); + if (!entry.isFile()) + continue; + + std::filesystem::create_directories(mh::copy(path).remove_filename()); + + std::ofstream file(path, std::ios::binary); + file.exceptions(std::ios::badbit | std::ios::failbit); + + const auto result = entry.readContent(file); + if (result != LIBZIPPP_OK) + { + throw std::runtime_error(mh::format("{}: entry.readContent() returned {}", + MH_SOURCE_LOCATION_CURRENT(), result)); + } + } + } + catch (...) + { + std::throw_with_nested(std::runtime_error(mh::format("{}: Failed to extract archive entries", + MH_SOURCE_LOCATION_CURRENT()))); + } + } + + static void SaveFile(const std::filesystem::path& path, const void* dataBegin, const void* dataEnd) + { + std::filesystem::create_directories(mh::copy(path).remove_filename()); + + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file.exceptions(std::ios::badbit | std::ios::failbit); + file.write(reinterpret_cast(dataBegin), + static_cast(dataEnd) - static_cast(dataBegin)); + } + + static void DownloadAndExtractZip(const HTTPClient& client, const URL& url, + const std::filesystem::path& extractDir) + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Downloading {}...", url); + const auto data = client.GetString(url); + + // Need to save to a file due to a libzippp bug in ZipArchive::fromBuffer + const auto tempZipPath = mh::copy(extractDir).replace_extension(".zip"); + Log(MH_SOURCE_LOCATION_CURRENT(), "Saving zip to {}...", tempZipPath); + + mh::scope_exit scopeExit([&] + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Deleting {}...", tempZipPath); + std::filesystem::remove(tempZipPath); + }); + + SaveFile(tempZipPath, data.data(), data.data() + data.size()); + + { + using namespace libzippp; + Log(MH_SOURCE_LOCATION_CURRENT(), "Extracting {} to {}...", tempZipPath, extractDir); + ZipArchive archive(tempZipPath.string()); + archive.open(); + ExtractArchive(archive, extractDir); + } + } + + std::future + UpdateManager::AvailableUpdate::RunPortableUpdate() const + { + if (!m_Updater) + throw std::logic_error("Updater was null, we should never get here"); + if (!m_Portable) + throw std::logic_error("Portable was null, we should never get here"); + + auto updaterVariant = *m_Updater; + auto toolVariant = *m_Portable; + auto client = m_HTTPClient; + return std::async([updaterVariant, toolVariant, client]() -> UpdateManager::UpdateToolResult + { + const auto tempDirRoot = + std::filesystem::temp_directory_path() / "TF2 Bot Detector" / "Portable Updates"; + const auto tempDirUpdater = tempDirRoot / mh::format("updater_{}", + std::chrono::high_resolution_clock::now().time_since_epoch().count()); + const auto tempDirTool = tempDirRoot / mh::format("tool_{}", + std::chrono::high_resolution_clock::now().time_since_epoch().count()); + + DownloadAndExtractZip(*client, updaterVariant.m_DownloadURL, tempDirUpdater); + DownloadAndExtractZip(*client, toolVariant.m_DownloadURL, tempDirTool); + + // FIXME linux + const auto updaterPath = tempDirUpdater / "tf2_bot_detector_updater.exe"; + const auto newVersionPath = tempDirTool; + const auto extractionPath = Platform::GetCurrentExeDir(); + const auto pid = Platform::Processes::GetCurrentProcessID(); + + // Run updater + Log(MH_SOURCE_LOCATION_CURRENT(), + "Launching updater..." + "\n\tUpdater Path: {}" + "\n\tNew version path: {}" + "\n\tExtraction path: {}" + "\n\tPID: {}", + updaterPath, + newVersionPath, + extractionPath, + pid + ); + + Platform::Processes::Launch(updaterPath, + { + "--portable-new-version-path", newVersionPath.string(), + "--portable-extraction-path", extractionPath.string(), + "--wait-pid", std::to_string(pid), + }); + + LogWarning(MH_SOURCE_LOCATION_CURRENT(), "Exiting now for portable-mode update..."); + std::exit(1); + __debugbreak(); + throw "TODO"; + }); } UpdateManager::BaseExceptionData::BaseExceptionData(const std::type_info& type, std::string message, diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index 264b8cc8..b4660b66 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -37,13 +37,13 @@ namespace tf2_bot_detector class IAvailableUpdate { public: - IAvailableUpdate(BuildInfo&& bi) : m_State(std::move(bi)) {} + IAvailableUpdate(BuildInfo&& bi) : m_BuildInfo(std::move(bi)) {} virtual ~IAvailableUpdate() = default; virtual bool CanSelfUpdate() const = 0; virtual void BeginSelfUpdate() const = 0; - BuildInfo m_State; + BuildInfo m_BuildInfo; }; enum class UpdateStatus diff --git a/tf2_bot_detector_updater/vcpkg.json b/tf2_bot_detector_updater/vcpkg.json index ab426cc6..6a5f8793 100644 --- a/tf2_bot_detector_updater/vcpkg.json +++ b/tf2_bot_detector_updater/vcpkg.json @@ -2,9 +2,6 @@ "name": "tf2-bot-detector", "version-string": "", "dependencies": [ - "openssl", - "cpp-httplib", - "libzippp", "fmt" ] } From 5a471451b0994d343b1988e1be1f09cab7e7500d Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 00:12:02 -0700 Subject: [PATCH 129/161] WIP automatic UAC elevation for when the target directory requires admin to write --- tf2_bot_detector/Platform/Platform.h | 5 +++- .../Platform/Windows/Processes.cpp | 25 +++++++++++++++++-- tf2_bot_detector/UpdateManager.cpp | 2 +- tf2_bot_detector_updater/CMakeLists.txt | 2 ++ tf2_bot_detector_updater/Resources.base.rc | 24 ++++++++++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tf2_bot_detector_updater/Resources.base.rc diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index d9466729..f6291402 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -62,6 +62,8 @@ namespace tf2_bot_detector std::future BeginInstallUpdate(const BuildInfo& bi, const HTTPClient& client); bool IsInstalled(); // As opposed to portable + bool NeedsElevationToWrite(const std::filesystem::path& path, bool recursive = false); + namespace Processes { bool IsTF2Running(); @@ -69,7 +71,8 @@ namespace tf2_bot_detector bool IsSteamRunning(); void RequireTF2NotRunning(); - void Launch(const std::filesystem::path& executable, const std::vector& args = {}); + void Launch(const std::filesystem::path& executable, const std::vector& args = {}, + bool elevated = false); int GetCurrentProcessID(); } diff --git a/tf2_bot_detector/Platform/Windows/Processes.cpp b/tf2_bot_detector/Platform/Windows/Processes.cpp index b3b03855..3007390f 100644 --- a/tf2_bot_detector/Platform/Windows/Processes.cpp +++ b/tf2_bot_detector/Platform/Windows/Processes.cpp @@ -14,6 +14,7 @@ #include #include +#include #include "WindowsHelpers.h" #include #include @@ -257,15 +258,34 @@ void tf2_bot_detector::Processes::RequireTF2NotRunning() } } -void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable, const std::vector& args) +void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable, + const std::vector& args, bool elevated) { std::wstring cmdLine; - cmdLine << executable << L' '; + //cmdLine << executable << L' '; for (const auto& arg : args) cmdLine << std::quoted(mh::change_encoding(arg)) << L' '; + const auto result = ShellExecuteW( + NULL, + elevated ? L"runas" : L"open", + executable.c_str(), + cmdLine.c_str(), + nullptr, + SW_SHOWDEFAULT); + + if (reinterpret_cast(result) <= 32) + { + auto exception = std::runtime_error( + mh::format("ShellExecuteW returned {}", reinterpret_cast(result))); + + LogException(MH_SOURCE_LOCATION_CURRENT(), exception); + throw exception; + } + +#if 0 STARTUPINFOW startupInfo{}; startupInfo.cb = sizeof(startupInfo); PROCESS_INFORMATION processInfo{}; @@ -293,6 +313,7 @@ void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable LogException(MH_SOURCE_LOCATION_CURRENT(), exception); throw exception; } +#endif } int tf2_bot_detector::Processes::GetCurrentProcessID() diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 8660efd0..d4d1ef7d 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -604,7 +604,7 @@ namespace "--portable-new-version-path", newVersionPath.string(), "--portable-extraction-path", extractionPath.string(), "--wait-pid", std::to_string(pid), - }); + }, true); LogWarning(MH_SOURCE_LOCATION_CURRENT(), "Exiting now for portable-mode update..."); std::exit(1); diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index 9f5d21d4..c75dd1a9 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -10,7 +10,9 @@ add_executable(tf2_bot_detector_updater ) if (WIN32) + configure_file(Resources.base.rc Resources.rc) target_sources(tf2_bot_detector_updater PRIVATE + "Resources.rc" "Update_MSIX.cpp" "Update_MSIX.h" ) diff --git a/tf2_bot_detector_updater/Resources.base.rc b/tf2_bot_detector_updater/Resources.base.rc new file mode 100644 index 00000000..a3cf40a5 --- /dev/null +++ b/tf2_bot_detector_updater/Resources.base.rc @@ -0,0 +1,24 @@ +#include +#include + +VS_VERSION_INFO VERSIONINFO +FILEVERSION ${CMAKE_PROJECT_VERSION_MAJOR},${CMAKE_PROJECT_VERSION_MINOR},${CMAKE_PROJECT_VERSION_PATCH},${CMAKE_PROJECT_VERSION_TWEAK} +PRODUCTVERSION ${CMAKE_PROJECT_VERSION_MAJOR},${CMAKE_PROJECT_VERSION_MINOR},${CMAKE_PROJECT_VERSION_PATCH},${CMAKE_PROJECT_VERSION_TWEAK} +FILEFLAGS ${TF2BD_RESOURCE_FILEFLAGS} +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "TF2 Bot Detector Updater" + VALUE "Comments", "Automatic updater for TF2 Bot Detector." + VALUE "FileVersion", "${CMAKE_PROJECT_VERSION}" + VALUE "ProductName", "TF2 Bot Detector Updater" + VALUE "ProductVersion", "${CMAKE_PROJECT_VERSION}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409,1200 + END +END From a77f602fb6aa1ddb0980da716557f395f2c6780b Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 00:25:04 -0700 Subject: [PATCH 130/161] Cache the tools directory as well --- .github/workflows/ccpp.yml | 6 ++++-- tf2_bot_detector_updater/Resources.base.rc | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f31dde8f..ba5b18a0 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -95,10 +95,11 @@ jobs: env: VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('vcpkg.json') }}-${{ matrix.triplet }} + key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('vcpkg.json') }}-${{ matrix.triplet }}-2 path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe + ${{ env.VCPKG_ROOT }}/downloads/tools - uses: seanmiddleditch/gha-setup-ninja@v2 - name: Configure build tools @@ -233,10 +234,11 @@ jobs: env: VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('tf2_bot_detector_updater/vcpkg.json') }}-${{ matrix.triplet }} + key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('tf2_bot_detector_updater/vcpkg.json') }}-${{ matrix.triplet }}-2 path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe + ${{ env.VCPKG_ROOT }}/downloads/tools - uses: seanmiddleditch/gha-setup-ninja@v2 - name: Configure build tools diff --git a/tf2_bot_detector_updater/Resources.base.rc b/tf2_bot_detector_updater/Resources.base.rc index a3cf40a5..007006ad 100644 --- a/tf2_bot_detector_updater/Resources.base.rc +++ b/tf2_bot_detector_updater/Resources.base.rc @@ -15,7 +15,7 @@ BEGIN VALUE "FileVersion", "${CMAKE_PROJECT_VERSION}" VALUE "ProductName", "TF2 Bot Detector Updater" VALUE "ProductVersion", "${CMAKE_PROJECT_VERSION}" - END + END END BLOCK "VarFileInfo" BEGIN From 024e2031388cb2880576ecdccbc1193e08e95806 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 01:37:14 -0700 Subject: [PATCH 131/161] Fixed missing version setting for the updater. --- tf2_bot_detector_updater/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index c75dd1a9..a12bd6e7 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.17) include(../cmake/init-preproject.cmake) - project(tf2_bot_detector_updater) + project(tf2_bot_detector_updater VERSION 1.0.0) include(../cmake/init-postproject.cmake) add_executable(tf2_bot_detector_updater From d1fe22b58334b0eb12854eb20a15f30a0ff7b026 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 02:51:13 -0700 Subject: [PATCH 132/161] Hopefully fixed the updater and tool versions not being in sync. --- .github/workflows/ccpp.yml | 10 ++++++---- CMakeLists.txt | 2 +- cmake/init-postproject.cmake | 23 +++++++++++++++++++---- cmake/init-preproject.cmake | 4 ++++ msix/New Text Document.txt | 0 tf2_bot_detector/CMakeLists.txt | 5 ----- tf2_bot_detector_updater/CMakeLists.txt | 2 +- 7 files changed, 31 insertions(+), 15 deletions(-) delete mode 100644 msix/New Text Document.txt diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index ba5b18a0..c780bb99 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -21,8 +21,8 @@ jobs: mkdir build_dir cd build_dir cmake ../ || true # we know this will fail, we just need version info from CMakeCache.txt - TF2BD_VERSION=`cat CMakeCache.txt | grep CMAKE_PROJECT_VERSION: | cut -d "=" -f2` - echo "::set-env name=TF2BD_VERSION::$TF2BD_VERSION.${{ github.run_number }}" + TF2BD_VERSION_NOBUILD=`cat CMakeCache.txt | grep TF2BD_VERSION_NOBUILD: | cut -d "=" -f2` + echo "::set-env name=TF2BD_VERSION::$TF2BD_VERSION_NOBUILD.${{ github.run_number }}" - name: Store TF2BD_VERSION uses: nick-invision/persist-action-data@v1 @@ -118,12 +118,13 @@ jobs: cd "${{ steps.tf2bd_paths.outputs.build_dir }}" cmake -G Ninja \ -DTF2BD_IS_CI_COMPILE=ON \ + --warn-uninitialized \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ - -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ + -DTF2BD_VERSION_BUILD=${{ github.run_number }} \ ../ cmake --build . --config ${{ matrix.build_type }} @@ -256,6 +257,7 @@ jobs: mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" cmake -G Ninja \ + --warn-uninitialized \ -DTF2BD_IS_CI_COMPILE=ON \ -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ -DVCPKG_CRT_LINKAGE=static \ @@ -265,7 +267,7 @@ jobs: -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="${{ steps.tf2bd_paths.outputs.build_dir }}" \ -DTF2BD_ENABLE_DISCORD_INTEGRATION=${{ matrix.discord_integration }} \ - -DCMAKE_PROJECT_VERSION_TWEAK=${{ github.run_number }} \ + -DTF2BD_VERSION_BUILD=${{ github.run_number }} \ ../tf2_bot_detector_updater/ cmake --build . --config ${{ matrix.build_type }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 58b2188b..33765150 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.17) include(cmake/init-preproject.cmake) - project(tf2_bot_detector VERSION 1.1.0) + project(tf2_bot_detector) include(cmake/init-postproject.cmake) add_subdirectory(submodules/ValveFileVDF) diff --git a/cmake/init-postproject.cmake b/cmake/init-postproject.cmake index 2f0df48d..333a17b7 100644 --- a/cmake/init-postproject.cmake +++ b/cmake/init-postproject.cmake @@ -1,11 +1,20 @@ message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") -# We use this as the build number. -message("TF2BD CMAKE_PROJECT_VERSION_TWEAK = ${CMAKE_PROJECT_VERSION_TWEAK}") -if ("${CMAKE_PROJECT_VERSION_TWEAK}" STREQUAL "") - set(CMAKE_PROJECT_VERSION_TWEAK 0) +if ("${TF2BD_VERSION_BUILD}" STREQUAL "") + set(TF2BD_VERSION_BUILD 0) endif() +set(TF2BD_VERSION_NOBUILD "${TF2BD_VERSION_MAJOR}.${TF2BD_VERSION_MINOR}.${TF2BD_VERSION_PATCH}" CACHE STRING "TF2BD version without the build number" FORCE) +set(TF2BD_VERSION "${TF2BD_VERSION_NOBUILD}.${TF2BD_VERSION_BUILD}" CACHE STRING "Full TF2BD version string" FORCE) + +# Prevent CMAKE_PROJECT_VERSION from getting out of sync with the individual components +set(CMAKE_PROJECT_VERSION_MAJOR ${TF2BD_VERSION_MAJOR} CACHE STRING "Loaded from TF2BD_VERSION_MAJOR." FORCE) +set(CMAKE_PROJECT_VERSION_MINOR ${TF2BD_VERSION_MINOR} CACHE STRING "Loaded from TF2BD_VERSION_MINOR." FORCE) +set(CMAKE_PROJECT_VERSION_PATCH ${TF2BD_VERSION_PATCH} CACHE STRING "Loaded from TF2BD_VERSION_PATCH." FORCE) +set(CMAKE_PROJECT_VERSION_TWEAK ${TF2BD_VERSION_BUILD} CACHE STRING "Loaded from TF2BD_VERSION_BUILD." FORCE) +set(CMAKE_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}.${CMAKE_PROJECT_VERSION_TWEAK}" CACHE STRING "Loaded from TF2BD_VERSION." FORCE) +message("TF2BD CMAKE_PROJECT_VERSION = ${CMAKE_PROJECT_VERSION}") + option(TF2BD_IS_CI_COMPILE "Set to true if this is a compile on a CI service. Used to help determine if user has made modifications to the source code." off) if (TF2BD_IS_CI_COMPILE) add_compile_definitions(TF2BD_IS_CI_COMPILE=1) @@ -43,4 +52,10 @@ if (MSVC) endif() add_definitions(/await) + + if ((CMAKE_BUILD_TYPE MATCHES "Release")) + set(TF2BD_RESOURCE_FILEFLAGS "0") + else() + set(TF2BD_RESOURCE_FILEFLAGS "VS_FF_DEBUG") + endif() endif() diff --git a/cmake/init-preproject.cmake b/cmake/init-preproject.cmake index b42f0b0f..e93b2749 100644 --- a/cmake/init-preproject.cmake +++ b/cmake/init-preproject.cmake @@ -1 +1,5 @@ cmake_policy(SET CMP0091 NEW) # Enable [CMAKE_]MSVC_RUNTIME_LIBRARY + +set(TF2BD_VERSION_MAJOR 1 CACHE STRING "TF2BD major version." FORCE) +set(TF2BD_VERSION_MINOR 1 CACHE STRING "TF2BD minor version." FORCE) +set(TF2BD_VERSION_PATCH 0 CACHE STRING "TF2BD patch version." FORCE) diff --git a/msix/New Text Document.txt b/msix/New Text Document.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index dee2a9ad..c6d2bab0 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -156,11 +156,6 @@ target_precompile_headers(tf2_bot_detector ) if(WIN32) - if ((CMAKE_BUILD_TYPE MATCHES "Release")) - set(TF2BD_RESOURCE_FILEFLAGS "0") - else() - set(TF2BD_RESOURCE_FILEFLAGS "VS_FF_DEBUG") - endif() configure_file(Resources.base.rc Resources.rc) target_sources(tf2_bot_detector PRIVATE diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index a12bd6e7..c75dd1a9 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.17) include(../cmake/init-preproject.cmake) - project(tf2_bot_detector_updater VERSION 1.0.0) + project(tf2_bot_detector_updater) include(../cmake/init-postproject.cmake) add_executable(tf2_bot_detector_updater From f4f40c2a6d88bc340b6a0d308c557a26cae050dd Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 03:56:45 -0700 Subject: [PATCH 133/161] ok, these warnings are incredibly unhelpful --- .github/workflows/ccpp.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index c780bb99..b0f5c1ce 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -118,7 +118,6 @@ jobs: cd "${{ steps.tf2bd_paths.outputs.build_dir }}" cmake -G Ninja \ -DTF2BD_IS_CI_COMPILE=ON \ - --warn-uninitialized \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ -DCMAKE_TOOLCHAIN_FILE="${{ env.RUNVCPKG_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake" \ -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="${{ steps.tf2bd_paths.outputs.build_dir }}" \ @@ -257,7 +256,6 @@ jobs: mkdir "${{ steps.tf2bd_paths.outputs.build_dir }}" cd "${{ steps.tf2bd_paths.outputs.build_dir }}" cmake -G Ninja \ - --warn-uninitialized \ -DTF2BD_IS_CI_COMPILE=ON \ -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ -DVCPKG_CRT_LINKAGE=static \ From 6e2325245ecc2a81ba8159735a2fd98557fa76b0 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 03:57:10 -0700 Subject: [PATCH 134/161] Make all projects use warnings-as-errors --- CMakeLists.txt | 5 ----- cmake/init-postproject.cmake | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33765150..c74dfb09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,3 @@ add_subdirectory("submodules/cppcoro") option(TF2BD_ENABLE_DISCORD_INTEGRATION "Enable discord integration" on) option(TF2BD_ENABLE_TESTS "Enable test compilation" off) - -# TODO: Find a way to do this locally -if(MSVC) - target_compile_options(tf2_bot_detector PRIVATE /WX) -endif() diff --git a/cmake/init-postproject.cmake b/cmake/init-postproject.cmake index 333a17b7..549825f0 100644 --- a/cmake/init-postproject.cmake +++ b/cmake/init-postproject.cmake @@ -58,4 +58,9 @@ if (MSVC) else() set(TF2BD_RESOURCE_FILEFLAGS "VS_FF_DEBUG") endif() + + # TODO: Find a way to do this locally + if(MSVC) + add_compile_options(/WX) + endif() endif() From 812beb52a984ab2fd0f3e23c84415b85b4939231 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 03:59:04 -0700 Subject: [PATCH 135/161] wip portable update, still need to elevate if there is a permissions error --- .../Platform/Windows/Processes.cpp | 30 ----- tf2_bot_detector/UpdateManager.cpp | 9 +- tf2_bot_detector_updater/CMakeLists.txt | 8 +- tf2_bot_detector_updater/Common.h | 9 +- tf2_bot_detector_updater/Platform/Platform.h | 16 +++ .../Platform/Windows/Platform_Windows.cpp | 47 +++++++ tf2_bot_detector_updater/Update_MSIX.cpp | 2 +- tf2_bot_detector_updater/Update_MSIX.h | 2 +- tf2_bot_detector_updater/Update_Portable.cpp | 33 +++++ tf2_bot_detector_updater/Update_Portable.h | 6 + tf2_bot_detector_updater/main.cpp | 125 ++++++++++++++---- 11 files changed, 224 insertions(+), 63 deletions(-) create mode 100644 tf2_bot_detector_updater/Platform/Platform.h create mode 100644 tf2_bot_detector_updater/Platform/Windows/Platform_Windows.cpp create mode 100644 tf2_bot_detector_updater/Update_Portable.cpp create mode 100644 tf2_bot_detector_updater/Update_Portable.h diff --git a/tf2_bot_detector/Platform/Windows/Processes.cpp b/tf2_bot_detector/Platform/Windows/Processes.cpp index 3007390f..43bf6218 100644 --- a/tf2_bot_detector/Platform/Windows/Processes.cpp +++ b/tf2_bot_detector/Platform/Windows/Processes.cpp @@ -284,36 +284,6 @@ void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable LogException(MH_SOURCE_LOCATION_CURRENT(), exception); throw exception; } - -#if 0 - STARTUPINFOW startupInfo{}; - startupInfo.cb = sizeof(startupInfo); - PROCESS_INFORMATION processInfo{}; - - const auto result = CreateProcessW( - nullptr, //mh::format(L"{}", executable).c_str(), - cmdLine.data(), - nullptr, - nullptr, - FALSE, - 0, - nullptr, - nullptr, - &startupInfo, - &processInfo); - - CloseHandle(processInfo.hThread); - CloseHandle(processInfo.hProcess); - - if (result == 0) - { - const auto err = GetLastError(); - auto exception = GetLastErrorException(E_FAIL, err, - mh::format("CreateProcessW() returned {}", MH_SOURCE_LOCATION_CURRENT(), result)); - LogException(MH_SOURCE_LOCATION_CURRENT(), exception); - throw exception; - } -#endif } int tf2_bot_detector::Processes::GetCurrentProcessID() diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index d4d1ef7d..79131b55 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -601,15 +601,14 @@ namespace Platform::Processes::Launch(updaterPath, { - "--portable-new-version-path", newVersionPath.string(), - "--portable-extraction-path", extractionPath.string(), + "--update-type", "Portable", + "--source-path", newVersionPath.string(), + "--dest-path", extractionPath.string(), "--wait-pid", std::to_string(pid), - }, true); + }); LogWarning(MH_SOURCE_LOCATION_CURRENT(), "Exiting now for portable-mode update..."); std::exit(1); - __debugbreak(); - throw "TODO"; }); } diff --git a/tf2_bot_detector_updater/CMakeLists.txt b/tf2_bot_detector_updater/CMakeLists.txt index c75dd1a9..236d7d8a 100644 --- a/tf2_bot_detector_updater/CMakeLists.txt +++ b/tf2_bot_detector_updater/CMakeLists.txt @@ -5,13 +5,19 @@ include(../cmake/init-preproject.cmake) include(../cmake/init-postproject.cmake) add_executable(tf2_bot_detector_updater - "main.cpp" + "Platform/Platform.h" "Common.h" + "main.cpp" + "Update_Portable.cpp" + "Update_Portable.h" ) +target_include_directories(tf2_bot_detector_updater PRIVATE .) + if (WIN32) configure_file(Resources.base.rc Resources.rc) target_sources(tf2_bot_detector_updater PRIVATE + "Platform/Windows/Platform_Windows.cpp" "Resources.rc" "Update_MSIX.cpp" "Update_MSIX.h" diff --git a/tf2_bot_detector_updater/Common.h b/tf2_bot_detector_updater/Common.h index 6dcc89c1..17708713 100644 --- a/tf2_bot_detector_updater/Common.h +++ b/tf2_bot_detector_updater/Common.h @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -20,13 +21,18 @@ namespace tf2_bot_detector::Updater // MSI -- maybe one day... #endif - //Portable, + Portable, }; struct CmdLineArgs { + bool m_PauseOnError = true; + UpdateType m_UpdateType = UpdateType::Unknown; ReleaseChannel m_ReleaseChannel = ReleaseChannel::None; + + std::filesystem::path m_SourcePath; + std::filesystem::path m_DestPath; }; extern CmdLineArgs s_CmdLineArgs; @@ -34,6 +40,7 @@ namespace tf2_bot_detector::Updater MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::Updater::UpdateType) MH_ENUM_REFLECT_VALUE(Unknown) + MH_ENUM_REFLECT_VALUE(Portable) #ifdef _WIN32 MH_ENUM_REFLECT_VALUE(MSIX) diff --git a/tf2_bot_detector_updater/Platform/Platform.h b/tf2_bot_detector_updater/Platform/Platform.h new file mode 100644 index 00000000..548a81dc --- /dev/null +++ b/tf2_bot_detector_updater/Platform/Platform.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace tf2_bot_detector +{ + inline namespace Platform + { + void WaitForPIDToExit(int pid); + + namespace Processes + { + void Launch(const std::filesystem::path& executable); + } + } +} diff --git a/tf2_bot_detector_updater/Platform/Windows/Platform_Windows.cpp b/tf2_bot_detector_updater/Platform/Windows/Platform_Windows.cpp new file mode 100644 index 00000000..14252a7f --- /dev/null +++ b/tf2_bot_detector_updater/Platform/Windows/Platform_Windows.cpp @@ -0,0 +1,47 @@ +#include "Platform/Platform.h" + +#include +#include + +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +void tf2_bot_detector::Platform::WaitForPIDToExit(int pid) +{ + if (auto handle = OpenProcess(SYNCHRONIZE, FALSE, pid)) + { + mh::scope_exit scopeExit([&] + { + CloseHandle(handle); + }); + + std::cerr << "Waiting for PID " << pid << " to exit..." << std::endl; + WaitForSingleObject(handle, INFINITE); + } + + std::cerr << "Done waiting for PID " << pid << " to exit, waiting a bit more to be safe..." << std::endl; + std::this_thread::sleep_for(250ms); +} + +void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable) +{ + const auto result = ShellExecuteW( + NULL, + L"open", + executable.c_str(), + L"", + nullptr, + SW_SHOWNORMAL); + + if (const auto iResult = reinterpret_cast(result); iResult <= 32) + { + auto msg = mh::format("{}: ShellExecuteW returned {}", __FUNCTION__, iResult); + std::cerr << msg << std::endl; + throw std::runtime_error(msg); + } +} diff --git a/tf2_bot_detector_updater/Update_MSIX.cpp b/tf2_bot_detector_updater/Update_MSIX.cpp index 5b88f5b2..270ca727 100644 --- a/tf2_bot_detector_updater/Update_MSIX.cpp +++ b/tf2_bot_detector_updater/Update_MSIX.cpp @@ -1,5 +1,5 @@ -#include "Common.h" #include "Update_MSIX.h" +#include "Common.h" #include #include diff --git a/tf2_bot_detector_updater/Update_MSIX.h b/tf2_bot_detector_updater/Update_MSIX.h index ae70ba12..bb19c972 100644 --- a/tf2_bot_detector_updater/Update_MSIX.h +++ b/tf2_bot_detector_updater/Update_MSIX.h @@ -2,5 +2,5 @@ namespace tf2_bot_detector::Updater { - int Update_MSIX(); + [[nodiscard]] int Update_MSIX(); } diff --git a/tf2_bot_detector_updater/Update_Portable.cpp b/tf2_bot_detector_updater/Update_Portable.cpp new file mode 100644 index 00000000..ee91a42f --- /dev/null +++ b/tf2_bot_detector_updater/Update_Portable.cpp @@ -0,0 +1,33 @@ +#include "Update_Portable.h" +#include "Platform/Platform.h" +#include "Common.h" + +#include + +#include +#include +#include + +int tf2_bot_detector::Updater::Update_Portable() try +{ + std::cerr << "Attempting to install portable version from " + << s_CmdLineArgs.m_SourcePath << " to " << s_CmdLineArgs.m_DestPath + << "..." << std::endl; + + using copy_options = std::filesystem::copy_options; + std::filesystem::copy(s_CmdLineArgs.m_SourcePath, s_CmdLineArgs.m_DestPath, + copy_options::recursive | copy_options::overwrite_existing); + + std::cerr << "Finished copying files. Attempting to start tool..." << std::endl; + + // FIXME linux + Platform::Processes::Launch(s_CmdLineArgs.m_DestPath / "tf2_bot_detector.exe"); + + return 0; +} +catch (const std::exception& e) +{ + std::cerr << mh::format("Unhandled exception ({}) in {}: {}", + typeid(e).name(), __FUNCTION__, e.what()) << std::endl; + return 3; +} diff --git a/tf2_bot_detector_updater/Update_Portable.h b/tf2_bot_detector_updater/Update_Portable.h new file mode 100644 index 00000000..0b848663 --- /dev/null +++ b/tf2_bot_detector_updater/Update_Portable.h @@ -0,0 +1,6 @@ +#pragma once + +namespace tf2_bot_detector::Updater +{ + [[nodiscard]] int Update_Portable(); +} diff --git a/tf2_bot_detector_updater/main.cpp b/tf2_bot_detector_updater/main.cpp index 8cdf0f01..7dfaddc4 100644 --- a/tf2_bot_detector_updater/main.cpp +++ b/tf2_bot_detector_updater/main.cpp @@ -1,8 +1,11 @@ #include "Common.h" +#include "Platform/Platform.h" #include "ReleaseChannel.h" +#include #include +#include #include #include @@ -15,7 +18,53 @@ using namespace tf2_bot_detector::Updater; CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; -static int MainHelper(int argc, char** argv) try +[[nodiscard]] static int ValidateParameters() +{ + if (s_CmdLineArgs.m_UpdateType == UpdateType::Unknown) + { + std::cerr << "Update type not specified. Use --update-type with one of the following values:\n" + << std::endl; + + for (const auto& value : mh::enum_type::VALUES) + { + if (value.value() == UpdateType::Unknown) + continue; + + std::cerr << '\t' << value.value_name() << '\n'; + } + + std::cerr << std::flush; + return 1; + } + + if (mh::any_eq(s_CmdLineArgs.m_UpdateType, UpdateType::MSIX)) + { + if (s_CmdLineArgs.m_ReleaseChannel == ReleaseChannel::None) + { + std::cerr << mh::format("Release channel not specified. Use --release-channel <{:v}|{:v}|{:v}>", + ReleaseChannel::Public, ReleaseChannel::Preview, ReleaseChannel::Nightly) << std::endl; + return 1; + } + } + + if (s_CmdLineArgs.m_UpdateType == UpdateType::Portable) + { + if (s_CmdLineArgs.m_SourcePath.empty()) + { + std::cerr << "Source path not specified. Use --source-path " << std::endl; + return 1; + } + if (s_CmdLineArgs.m_DestPath.empty()) + { + std::cerr << "Destination path not specified. Use --dest-path " << std::endl; + return 1; + } + } + + return 0; +} + +[[nodiscard]] static int MainHelper(int argc, char** argv) try { for (int i = 1; i < argc; i++) { @@ -25,7 +74,7 @@ static int MainHelper(int argc, char** argv) try { if (!hasNextArg) { - std::cerr << "Missing argument for " << arg << std::endl; + std::cerr << "Missing argument for " << std::quoted(arg) << std::endl; return 1; } @@ -35,46 +84,69 @@ static int MainHelper(int argc, char** argv) try { if (!hasNextArg) { - std::cerr << "Missing argument for " << arg << std::endl; + std::cerr << "Missing argument for " << std::quoted(arg) << std::endl; return 1; } s_CmdLineArgs.m_ReleaseChannel = mh::enum_type::find_value(argv[i + 1]); } - } - - if (s_CmdLineArgs.m_ReleaseChannel == ReleaseChannel::None) - { - std::cerr << mh::format("Release channel not specified. Use --release-channel <{:v}|{:v}|{:v}>", - ReleaseChannel::Public, ReleaseChannel::Preview, ReleaseChannel::Nightly); - return 1; - } - - if (s_CmdLineArgs.m_UpdateType == UpdateType::Unknown) - { - std::cerr << "Update type not specified. Use --update-type with one of the following values:\n"; + else if (arg == "--source-path") + { + if (!hasNextArg) + { + std::cerr << "Missing argument for " << std::quoted(arg) << std::endl; + return 1; + } - for (const auto& value : mh::enum_type::VALUES) + s_CmdLineArgs.m_SourcePath = argv[i + 1]; + } + else if (arg == "--dest-path") { - if (value.value() == UpdateType::Unknown) - continue; + if (!hasNextArg) + { + std::cerr << "Missing argument for " << std::quoted(arg) << std::endl; + return 1; + } - std::cerr << '\t' << value.value_name() << '\n'; + s_CmdLineArgs.m_DestPath = argv[i + 1]; } + else if (arg == "--wait-pid") + { + if (!hasNextArg) + { + std::cerr << "Missing argument for " << std::quoted(arg) << std::endl; + return 1; + } - std::cerr << std::flush; - return 1; + int pid; + if (sscanf_s(argv[i + 1], "%i", &pid) != 1) + { + std::cerr << "Unable to parse argument to " << std::quoted(arg) + << ' ' << std::quoted(argv[i + 1]) << " as an integer." << std::endl; + return 1; + } + + Platform::WaitForPIDToExit(pid); + } + else if (arg == "--no-pause-on-error") + { + s_CmdLineArgs.m_PauseOnError = false; + } } + if (auto result = ValidateParameters(); result != 0) + return result; + switch (s_CmdLineArgs.m_UpdateType) { #ifdef _WIN32 case UpdateType::MSIX: return Update_MSIX(); #endif + default: + std::cerr << "Unhandled UpdateType(" << int(s_CmdLineArgs.m_UpdateType) << ')' << std::endl; + return 1; } - - return 0; } catch (const std::exception& e) { @@ -85,7 +157,12 @@ catch (const std::exception& e) int main(int argc, char** argv) { std::cerr << std::endl; - MainHelper(argc, argv); + auto result = MainHelper(argc, argv); std::cerr << std::endl << std::endl; + + if (result != 0 && s_CmdLineArgs.m_PauseOnError) + system("pause"); + + return result; } From eb38843579ec5f6be439f3994b099ec2f066aa20 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 13:53:04 -0700 Subject: [PATCH 136/161] Try uploading nightly builds to tf2bd-util. --- .github/workflows/ccpp.yml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index b0f5c1ce..9c2ad97d 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -144,21 +144,22 @@ jobs: path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" - if: startsWith(matrix.os, 'windows') + if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') shell: bash run: | cp -v ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe ${{ steps.tf2bd_paths.outputs.build_dir }}/*.dll ${{ steps.tf2bd_paths.outputs.workspace }}/staging/ - - name: Smartscreen workaround - if: ${{ startsWith(matrix.os, 'windows') }} - shell: bash - run: | + echo "Performing smartscreen workaround..." echo "Hash of current exe: " sha1sum "${{ steps.tf2bd_paths.outputs.workspace }}/staging/tf2_bot_detector.exe" cp -v "${{ steps.tf2bd_paths.outputs.workspace }}/smartscreen/${{ matrix.tf2bd_arch }}/tf2_bot_detector.exe" "${{ steps.tf2bd_paths.outputs.workspace }}/staging/tf2_bot_detector.exe" echo "Hash of cached exe: " sha1sum "${{ steps.tf2bd_paths.outputs.workspace }}/staging/tf2_bot_detector.exe" + echo "Uploading artifact to tf2bd-util..." + cd "${{ steps.tf2bd_paths.outputs.workspace }}/staging/" + zip -r -9 - * | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolPortable&os=windows&arch=${{ matrix.tf2bd_arch }}" + - name: "Artifacts: Upload staging/" if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') uses: actions/upload-artifact@v2 @@ -277,6 +278,12 @@ jobs: certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + - name: "Artifacts: tf2_bot_detector_updater -> tf2bd-util nightly archive" + run: | + echo "Uploading artifact to tf2bd-util..." + cd "${{ steps.tf2bd_paths.outputs.build_dir }}" + zip -r -9 - *.dll *.exe | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=Updater&os=windows&arch=${{ matrix.tf2bd_arch }}" + - name: "Artifacts: tf2_bot_detector_updater" uses: actions/upload-artifact@v2 with: @@ -419,6 +426,11 @@ jobs: certificate: '${{ secrets.CERTIFICATE_PFX_BASE64 }}' password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' + - name: "Artifacts: msix bundle -> tf2bd-util nightly archive" + run: | + echo "Uploading artifact to tf2bd-util..." + curl -T "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolMSIXBundle&os=windows&arch=AnyCPU" + - name: Upload bundle (github actions artifact) uses: actions/upload-artifact@v2 with: From 361527c544185781eda7a7031f844f6753a7b357 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 14:02:09 -0700 Subject: [PATCH 137/161] Use 7z instead of info-zip --- .github/workflows/ccpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 9c2ad97d..eb955e11 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -158,7 +158,7 @@ jobs: echo "Uploading artifact to tf2bd-util..." cd "${{ steps.tf2bd_paths.outputs.workspace }}/staging/" - zip -r -9 - * | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolPortable&os=windows&arch=${{ matrix.tf2bd_arch }}" + 7z a -so data.zip * | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolPortable&os=windows&arch=${{ matrix.tf2bd_arch }}" - name: "Artifacts: Upload staging/" if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') @@ -282,7 +282,7 @@ jobs: run: | echo "Uploading artifact to tf2bd-util..." cd "${{ steps.tf2bd_paths.outputs.build_dir }}" - zip -r -9 - *.dll *.exe | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=Updater&os=windows&arch=${{ matrix.tf2bd_arch }}" + 7z a -so data.zip *.dll *.exe | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=Updater&os=windows&arch=${{ matrix.tf2bd_arch }}" - name: "Artifacts: tf2_bot_detector_updater" uses: actions/upload-artifact@v2 From 280b89b2ee595444d9f85efa74a1adc2307e8983 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 14:12:36 -0700 Subject: [PATCH 138/161] Only upload release builds to tf2bd-util. --- .github/workflows/ccpp.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index eb955e11..89dc4a6e 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -8,6 +8,10 @@ on: - '*.md' - '*.appinstaller_template' +defaults: + run: + shell: bash + jobs: setup_version: runs-on: windows-latest @@ -16,7 +20,6 @@ jobs: uses: actions/checkout@v2 - name: Extract TF2BD_VERSION - shell: bash run: | mkdir build_dir cd build_dir @@ -61,7 +64,6 @@ jobs: - name: Config cross-platform paths id: tf2bd_paths - shell: bash run: | tf2bd_workspace=`realpath "${{ github.workspace }}"` echo "::set-output name=workspace::$tf2bd_workspace" @@ -109,7 +111,6 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} - shell: bash env: VCPKG_FEATURE_FLAGS: manifests RUNVCPKG_VCPKG_ROOT: ${{ github.workspace }}/submodules/vcpkg @@ -144,8 +145,7 @@ jobs: path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" - if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') - shell: bash + if: matrix.build_type == "Release" && env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') run: | cp -v ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe ${{ steps.tf2bd_paths.outputs.build_dir }}/*.dll ${{ steps.tf2bd_paths.outputs.workspace }}/staging/ @@ -202,7 +202,6 @@ jobs: - name: Config cross-platform paths id: tf2bd_paths - shell: bash run: | tf2bd_workspace=`realpath "${{ github.workspace }}"` echo "::set-output name=workspace::$tf2bd_workspace" @@ -249,7 +248,6 @@ jobs: - name: CMake if: ${{ startsWith(matrix.os, 'windows') }} - shell: bash env: VCPKG_FEATURE_FLAGS: manifests RUNVCPKG_VCPKG_ROOT: ${{ github.workspace }}/submodules/vcpkg @@ -279,6 +277,7 @@ jobs: password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - name: "Artifacts: tf2_bot_detector_updater -> tf2bd-util nightly archive" + if: matrix.build_type == "Release" run: | echo "Uploading artifact to tf2bd-util..." cd "${{ steps.tf2bd_paths.outputs.build_dir }}" @@ -344,11 +343,9 @@ jobs: path: msix/package_staging/app - name: Mark as non-portable - shell: bash run: touch msix/package_staging/app/cfg/.non_portable - name: Find and replace data in package config - shell: bash run: | cat msix/package_staging/AppxManifest.xml \ | sed 's/TF2BD_PROCESSOR_ARCH_REPLACE_ME/${{ matrix.tf2bd_arch }}/g' \ @@ -411,7 +408,6 @@ jobs: path: msix/bundle_staging - name: Config msixbundle name - shell: bash run: echo "::set-env name=TF2BD_MSIXBUNDLE_NAME::tf2-bot-detector_windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msixbundle" - name: Create bundle @@ -427,6 +423,7 @@ jobs: password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - name: "Artifacts: msix bundle -> tf2bd-util nightly archive" + if: matrix.build_type.name == "Release" run: | echo "Uploading artifact to tf2bd-util..." curl -T "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolMSIXBundle&os=windows&arch=AnyCPU" From 5ce428a7d822fb90d62751a4e1ae95465e53cb76 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 14:13:25 -0700 Subject: [PATCH 139/161] Single quotes required? --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 89dc4a6e..d70972ff 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -145,7 +145,7 @@ jobs: path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" - if: matrix.build_type == "Release" && env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') + if: matrix.build_type == 'Release' && env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') run: | cp -v ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe ${{ steps.tf2bd_paths.outputs.build_dir }}/*.dll ${{ steps.tf2bd_paths.outputs.workspace }}/staging/ @@ -277,7 +277,7 @@ jobs: password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - name: "Artifacts: tf2_bot_detector_updater -> tf2bd-util nightly archive" - if: matrix.build_type == "Release" + if: matrix.build_type == 'Release' run: | echo "Uploading artifact to tf2bd-util..." cd "${{ steps.tf2bd_paths.outputs.build_dir }}" @@ -423,7 +423,7 @@ jobs: password: '${{ secrets.CERTIFICATE_PFX_PASSWORD }}' - name: "Artifacts: msix bundle -> tf2bd-util nightly archive" - if: matrix.build_type.name == "Release" + if: matrix.build_type.name == 'Release' run: | echo "Uploading artifact to tf2bd-util..." curl -T "msix/${{ env.TF2BD_MSIXBUNDLE_NAME }}" "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolMSIXBundle&os=windows&arch=AnyCPU" From fc0aca61761d2ad0cf5a413ebce64bd7f643e716 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 14:25:21 -0700 Subject: [PATCH 140/161] Revert shell for makeappx --- .github/workflows/ccpp.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index d70972ff..f105ebee 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -356,6 +356,7 @@ jobs: | tee msix/package_staging/AppxManifest.xml - name: Create msix package + shell: cmd run: | cd msix makeappx.exe pack /d package_staging /p ${{ env.TF2BD_MSIX_FILENAME }} @@ -411,6 +412,7 @@ jobs: run: echo "::set-env name=TF2BD_MSIXBUNDLE_NAME::tf2-bot-detector_windows_${{ env.TF2BD_VERSION }}_${{ matrix.build_type.name }}.msixbundle" - name: Create bundle + shell: cmd run: | cd msix makeappx bundle /d bundle_staging /bv ${{ env.TF2BD_VERSION }} /p ${{ env.TF2BD_MSIXBUNDLE_NAME }} From cf961fa3985661f38e6ec912ee30a29e8a6c8825 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 14:37:26 -0700 Subject: [PATCH 141/161] Fixed debug build artifacts being incomplete. --- .github/workflows/ccpp.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f105ebee..5a57e630 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -145,8 +145,9 @@ jobs: path: "${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe" - name: "Artifacts: Prepare staging/" - if: matrix.build_type == 'Release' && env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') + if: env.TF2BD_ENABLE_ARTIFACT_UPLOAD && startsWith(matrix.os, 'windows') run: | + echo "Copying build artifacts to staging..." cp -v ${{ steps.tf2bd_paths.outputs.build_dir }}/*.exe ${{ steps.tf2bd_paths.outputs.build_dir }}/*.dll ${{ steps.tf2bd_paths.outputs.workspace }}/staging/ echo "Performing smartscreen workaround..." @@ -156,6 +157,9 @@ jobs: echo "Hash of cached exe: " sha1sum "${{ steps.tf2bd_paths.outputs.workspace }}/staging/tf2_bot_detector.exe" + - name: "Artifacts: staging -> tf2bd-util nightly archive" + if: matrix.build_type.name == 'Release' && env.TF2BD_ENABLE_ARTIFACT_UPLOAD + run: | echo "Uploading artifact to tf2bd-util..." cd "${{ steps.tf2bd_paths.outputs.workspace }}/staging/" 7z a -so data.zip * | curl -T - "https://tf2bd-util.pazer.us/NightlyArchive/UploadArtifact?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&artifactType=MainToolPortable&os=windows&arch=${{ matrix.tf2bd_arch }}" From da5da50b88b8f65525e240257cd8cedb5b837bf0 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 15:01:43 -0700 Subject: [PATCH 142/161] Fixed portable builds not being uploaded. --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 5a57e630..53a49876 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -158,7 +158,7 @@ jobs: sha1sum "${{ steps.tf2bd_paths.outputs.workspace }}/staging/tf2_bot_detector.exe" - name: "Artifacts: staging -> tf2bd-util nightly archive" - if: matrix.build_type.name == 'Release' && env.TF2BD_ENABLE_ARTIFACT_UPLOAD + if: matrix.build_type == 'Release' && env.TF2BD_ENABLE_ARTIFACT_UPLOAD run: | echo "Uploading artifact to tf2bd-util..." cd "${{ steps.tf2bd_paths.outputs.workspace }}/staging/" From 5ecbba47903bd9c9fbb6591634cfaf8ca1fdb22c Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 16:10:09 -0700 Subject: [PATCH 143/161] Fixed updater issue preventing portable updates. --- tf2_bot_detector_updater/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tf2_bot_detector_updater/main.cpp b/tf2_bot_detector_updater/main.cpp index 7dfaddc4..c9cef34b 100644 --- a/tf2_bot_detector_updater/main.cpp +++ b/tf2_bot_detector_updater/main.cpp @@ -9,6 +9,7 @@ #include #include +#include "Update_Portable.h" #ifdef _WIN32 #include "Update_MSIX.h" #endif @@ -143,8 +144,11 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; case UpdateType::MSIX: return Update_MSIX(); #endif + case UpdateType::Portable: + return Update_Portable(); + default: - std::cerr << "Unhandled UpdateType(" << int(s_CmdLineArgs.m_UpdateType) << ')' << std::endl; + std::cerr << mh::format("Unhandled UpdateType {}", s_CmdLineArgs.m_UpdateType) << std::endl; return 1; } } From 270241c1842d9145b1cf3ddbcbcf8847ae1bd918 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 19:27:39 -0700 Subject: [PATCH 144/161] Made the formatting of enum class types without mh::enum_fmt a compile-time error. --- submodules/mh_stuff | 2 +- tf2_bot_detector/Config/PlayerListJSON.h | 23 +-- tf2_bot_detector/ConsoleLog/ConsoleLines.cpp | 2 +- tf2_bot_detector/Filesystem.cpp | 2 +- tf2_bot_detector/Filesystem.h | 24 +-- tf2_bot_detector/GameData/UserMessageType.h | 184 ++++++++---------- tf2_bot_detector/Log.h | 22 ++- tf2_bot_detector/ModeratorLogic.cpp | 4 +- .../SetupFlow/UpdateCheckPage.cpp | 4 +- tf2_bot_detector/UI/ImGui_TF2BotDetector.h | 6 +- tf2_bot_detector/UI/MainWindow.Scoreboard.cpp | 9 +- 11 files changed, 127 insertions(+), 155 deletions(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index a916f3c5..2c3f4228 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit a916f3c555e74c74f506d706324724cc3063cb4d +Subproject commit 2c3f4228ca7a68baef3708175f7220bfb3cd1647 diff --git a/tf2_bot_detector/Config/PlayerListJSON.h b/tf2_bot_detector/Config/PlayerListJSON.h index 419389c0..63858bf5 100644 --- a/tf2_bot_detector/Config/PlayerListJSON.h +++ b/tf2_bot_detector/Config/PlayerListJSON.h @@ -206,23 +206,12 @@ namespace tf2_bot_detector void from_json(const nlohmann::json& j, PlayerAttribute& d); } -template -std::basic_ostream& operator<<(std::basic_ostream& os, tf2_bot_detector::PlayerAttribute type) -{ - using tf2_bot_detector::PlayerAttribute; - - switch (type) - { - case PlayerAttribute::Cheater: return os << "Cheater"; - case PlayerAttribute::Exploiter: return os << "Exploiter"; - case PlayerAttribute::Racist: return os << "Racist"; - case PlayerAttribute::Suspicious: return os << "Suspicious"; - - default: - assert(!"Unknown PlayerAttribute"); - return os << ""; - } -} +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::PlayerAttribute) + MH_ENUM_REFLECT_VALUE(Cheater) + MH_ENUM_REFLECT_VALUE(Exploiter) + MH_ENUM_REFLECT_VALUE(Racist) + MH_ENUM_REFLECT_VALUE(Suspicious) +MH_ENUM_REFLECT_END() template std::basic_ostream& operator<<(std::basic_ostream& os, diff --git a/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp b/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp index 735b5a42..07c49c8a 100644 --- a/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp +++ b/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp @@ -673,7 +673,7 @@ bool SVCUserMessageLine::ShouldPrint() const void SVCUserMessageLine::Print(const PrintArgs& args) const { if (IsSpecial(m_MsgType)) - ImGui::TextFmt({ 0, 1, 1, 1 }, "{}", m_MsgType); + ImGui::TextFmt({ 0, 1, 1, 1 }, "{}", mh::enum_fmt(m_MsgType)); else ImGui::TextFmt("Msg from {}: svc_UserMessage: type {}, bytes {}", m_Address, int(m_MsgType), m_MsgBytes); } diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp index 6ef92e68..39202220 100644 --- a/tf2_bot_detector/Filesystem.cpp +++ b/tf2_bot_detector/Filesystem.cpp @@ -126,7 +126,7 @@ std::filesystem::path Filesystem::ResolvePath(const std::filesystem::path& path, } }); - DebugLog("ResolvePath({}, {}) -> {}", path, usage, retVal); + DebugLog("ResolvePath({}, {}) -> {}", path, mh::enum_fmt(usage), retVal); return std::move(retVal); } diff --git a/tf2_bot_detector/Filesystem.h b/tf2_bot_detector/Filesystem.h index 239f4c4e..f97f4ae2 100644 --- a/tf2_bot_detector/Filesystem.h +++ b/tf2_bot_detector/Filesystem.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -64,22 +65,7 @@ namespace tf2_bot_detector }; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, tf2_bot_detector::PathUsage usage) -{ - using namespace tf2_bot_detector; - -#undef OS_CASE -#define OS_CASE(x) case x : return os << #x - - switch (usage) - { - OS_CASE(PathUsage::Read); - OS_CASE(PathUsage::Write); - - default: - return os << "PathUsage(" << +std::underlying_type_t(usage) << ')'; - } - -#undef OS_CASE -} +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::PathUsage) + MH_ENUM_REFLECT_VALUE(Read) + MH_ENUM_REFLECT_VALUE(Write) +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector/GameData/UserMessageType.h b/tf2_bot_detector/GameData/UserMessageType.h index 5c8bc122..119b685e 100644 --- a/tf2_bot_detector/GameData/UserMessageType.h +++ b/tf2_bot_detector/GameData/UserMessageType.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace tf2_bot_detector { @@ -93,100 +93,88 @@ namespace tf2_bot_detector }; } -template -std::basic_ostream& operator<<(std::basic_ostream& os, tf2_bot_detector::UserMessageType type) -{ - using tf2_bot_detector::UserMessageType; -#undef OS_CASE -#define OS_CASE(v) case v : return os << #v - switch (type) - { - OS_CASE(UserMessageType::Geiger); - OS_CASE(UserMessageType::Train); - OS_CASE(UserMessageType::HudText); - OS_CASE(UserMessageType::SayText); - OS_CASE(UserMessageType::SayText2); - OS_CASE(UserMessageType::TextMsg); - OS_CASE(UserMessageType::ResetHUD); - OS_CASE(UserMessageType::GameTitle); - OS_CASE(UserMessageType::ItemPickup); - OS_CASE(UserMessageType::ShowMenu); - OS_CASE(UserMessageType::Shake); - OS_CASE(UserMessageType::Fade); - OS_CASE(UserMessageType::VGUIMenu); - OS_CASE(UserMessageType::Rumble); - OS_CASE(UserMessageType::CloseCaption); - OS_CASE(UserMessageType::SendAudio); - OS_CASE(UserMessageType::VoiceMask); - OS_CASE(UserMessageType::RequestState); - OS_CASE(UserMessageType::Damage); - OS_CASE(UserMessageType::HintText); - OS_CASE(UserMessageType::KeyHintText); - OS_CASE(UserMessageType::HudMsg); - OS_CASE(UserMessageType::AmmoDenied); - OS_CASE(UserMessageType::AchievementEvent); - OS_CASE(UserMessageType::UpdateRadar); - OS_CASE(UserMessageType::VoiceSubtitle); - OS_CASE(UserMessageType::HudNotify); - OS_CASE(UserMessageType::HudNotifyCustom); - OS_CASE(UserMessageType::PlayerStatsUpdate); - OS_CASE(UserMessageType::MapStatsUpdate); - OS_CASE(UserMessageType::PlayerIgnited); - OS_CASE(UserMessageType::PlayerIgnitedInv); - OS_CASE(UserMessageType::HudArenaNotify); - OS_CASE(UserMessageType::UpdateAchievement); - OS_CASE(UserMessageType::TrainingMsg); - OS_CASE(UserMessageType::TrainingObjective); - OS_CASE(UserMessageType::DamageDodged); - OS_CASE(UserMessageType::PlayerJarated); - OS_CASE(UserMessageType::PlayerExtinguished); - OS_CASE(UserMessageType::PlayerJaratedFade); - OS_CASE(UserMessageType::PlayerShieldBlocked); - OS_CASE(UserMessageType::BreakModel); - OS_CASE(UserMessageType::CheapBreakModel); - OS_CASE(UserMessageType::BreakModel_Pumpkin); - OS_CASE(UserMessageType::BreakModelRocketDud); - OS_CASE(UserMessageType::CallVoteFailed); - OS_CASE(UserMessageType::VoteStart); - OS_CASE(UserMessageType::VotePass); - OS_CASE(UserMessageType::VoteFailed); - OS_CASE(UserMessageType::VoteSetup); - OS_CASE(UserMessageType::PlayerBonusPoints); - OS_CASE(UserMessageType::RDTeamPointsChanged); - OS_CASE(UserMessageType::SpawnFlyingBird); - OS_CASE(UserMessageType::PlayerGodRayEffect); - OS_CASE(UserMessageType::PlayerTeleportHomeEffect); - OS_CASE(UserMessageType::MVMStatsReset); - OS_CASE(UserMessageType::MVMPlayerEvent); - OS_CASE(UserMessageType::MVMResetPlayerStats); - OS_CASE(UserMessageType::MVMWaveFailed); - OS_CASE(UserMessageType::MVMAnnouncement); - OS_CASE(UserMessageType::MVMPlayerUpgradedEvent); - OS_CASE(UserMessageType::MVMVictory); - OS_CASE(UserMessageType::MVMWaveChange); - OS_CASE(UserMessageType::MVMLocalPlayerUpgradesClear); - OS_CASE(UserMessageType::MVMLocalPlayerUpgradesValue); - OS_CASE(UserMessageType::MVMLocalPlayerWaveSpendingStats); - OS_CASE(UserMessageType::MVMLocalPlayerWaveSpendingValue); - OS_CASE(UserMessageType::MVMLocalPlayerUpgradeSpending); - OS_CASE(UserMessageType::MVMServerKickTimeUpdate); - OS_CASE(UserMessageType::PlayerLoadoutUpdated); - OS_CASE(UserMessageType::PlayerTauntSoundLoopStart); - OS_CASE(UserMessageType::PlayerTauntSoundLoopEnd); - OS_CASE(UserMessageType::ForcePlayerViewAngles); - OS_CASE(UserMessageType::BonusDucks); - OS_CASE(UserMessageType::EOTLDuckEvent); - OS_CASE(UserMessageType::PlayerPickupWeapon); - OS_CASE(UserMessageType::QuestObjectiveCompleted); - OS_CASE(UserMessageType::SPHapWeapEvent); - OS_CASE(UserMessageType::HapDmg); - OS_CASE(UserMessageType::HapPunch); - OS_CASE(UserMessageType::HapSetDrag); - OS_CASE(UserMessageType::HapSetConst); - OS_CASE(UserMessageType::HapMeleeContact); - - default: - return os << "UserMessageType(" << +std::underlying_type_t(type) << ')'; - } -#undef OS_CASE -} +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UserMessageType) + MH_ENUM_REFLECT_VALUE(Geiger) + MH_ENUM_REFLECT_VALUE(Train) + MH_ENUM_REFLECT_VALUE(HudText) + MH_ENUM_REFLECT_VALUE(SayText) + MH_ENUM_REFLECT_VALUE(SayText2) + MH_ENUM_REFLECT_VALUE(TextMsg) + MH_ENUM_REFLECT_VALUE(ResetHUD) + MH_ENUM_REFLECT_VALUE(GameTitle) + MH_ENUM_REFLECT_VALUE(ItemPickup) + MH_ENUM_REFLECT_VALUE(ShowMenu) + MH_ENUM_REFLECT_VALUE(Shake) + MH_ENUM_REFLECT_VALUE(Fade) + MH_ENUM_REFLECT_VALUE(VGUIMenu) + MH_ENUM_REFLECT_VALUE(Rumble) + MH_ENUM_REFLECT_VALUE(CloseCaption) + MH_ENUM_REFLECT_VALUE(SendAudio) + MH_ENUM_REFLECT_VALUE(VoiceMask) + MH_ENUM_REFLECT_VALUE(RequestState) + MH_ENUM_REFLECT_VALUE(Damage) + MH_ENUM_REFLECT_VALUE(HintText) + MH_ENUM_REFLECT_VALUE(KeyHintText) + MH_ENUM_REFLECT_VALUE(HudMsg) + MH_ENUM_REFLECT_VALUE(AmmoDenied) + MH_ENUM_REFLECT_VALUE(AchievementEvent) + MH_ENUM_REFLECT_VALUE(UpdateRadar) + MH_ENUM_REFLECT_VALUE(VoiceSubtitle) + MH_ENUM_REFLECT_VALUE(HudNotify) + MH_ENUM_REFLECT_VALUE(HudNotifyCustom) + MH_ENUM_REFLECT_VALUE(PlayerStatsUpdate) + MH_ENUM_REFLECT_VALUE(MapStatsUpdate) + MH_ENUM_REFLECT_VALUE(PlayerIgnited) + MH_ENUM_REFLECT_VALUE(PlayerIgnitedInv) + MH_ENUM_REFLECT_VALUE(HudArenaNotify) + MH_ENUM_REFLECT_VALUE(UpdateAchievement) + MH_ENUM_REFLECT_VALUE(TrainingMsg) + MH_ENUM_REFLECT_VALUE(TrainingObjective) + MH_ENUM_REFLECT_VALUE(DamageDodged) + MH_ENUM_REFLECT_VALUE(PlayerJarated) + MH_ENUM_REFLECT_VALUE(PlayerExtinguished) + MH_ENUM_REFLECT_VALUE(PlayerJaratedFade) + MH_ENUM_REFLECT_VALUE(PlayerShieldBlocked) + MH_ENUM_REFLECT_VALUE(BreakModel) + MH_ENUM_REFLECT_VALUE(CheapBreakModel) + MH_ENUM_REFLECT_VALUE(BreakModel_Pumpkin) + MH_ENUM_REFLECT_VALUE(BreakModelRocketDud) + MH_ENUM_REFLECT_VALUE(CallVoteFailed) + MH_ENUM_REFLECT_VALUE(VoteStart) + MH_ENUM_REFLECT_VALUE(VotePass) + MH_ENUM_REFLECT_VALUE(VoteFailed) + MH_ENUM_REFLECT_VALUE(VoteSetup) + MH_ENUM_REFLECT_VALUE(PlayerBonusPoints) + MH_ENUM_REFLECT_VALUE(RDTeamPointsChanged) + MH_ENUM_REFLECT_VALUE(SpawnFlyingBird) + MH_ENUM_REFLECT_VALUE(PlayerGodRayEffect) + MH_ENUM_REFLECT_VALUE(PlayerTeleportHomeEffect) + MH_ENUM_REFLECT_VALUE(MVMStatsReset) + MH_ENUM_REFLECT_VALUE(MVMPlayerEvent) + MH_ENUM_REFLECT_VALUE(MVMResetPlayerStats) + MH_ENUM_REFLECT_VALUE(MVMWaveFailed) + MH_ENUM_REFLECT_VALUE(MVMAnnouncement) + MH_ENUM_REFLECT_VALUE(MVMPlayerUpgradedEvent) + MH_ENUM_REFLECT_VALUE(MVMVictory) + MH_ENUM_REFLECT_VALUE(MVMWaveChange) + MH_ENUM_REFLECT_VALUE(MVMLocalPlayerUpgradesClear) + MH_ENUM_REFLECT_VALUE(MVMLocalPlayerUpgradesValue) + MH_ENUM_REFLECT_VALUE(MVMLocalPlayerWaveSpendingStats) + MH_ENUM_REFLECT_VALUE(MVMLocalPlayerWaveSpendingValue) + MH_ENUM_REFLECT_VALUE(MVMLocalPlayerUpgradeSpending) + MH_ENUM_REFLECT_VALUE(MVMServerKickTimeUpdate) + MH_ENUM_REFLECT_VALUE(PlayerLoadoutUpdated) + MH_ENUM_REFLECT_VALUE(PlayerTauntSoundLoopStart) + MH_ENUM_REFLECT_VALUE(PlayerTauntSoundLoopEnd) + MH_ENUM_REFLECT_VALUE(ForcePlayerViewAngles) + MH_ENUM_REFLECT_VALUE(BonusDucks) + MH_ENUM_REFLECT_VALUE(EOTLDuckEvent) + MH_ENUM_REFLECT_VALUE(PlayerPickupWeapon) + MH_ENUM_REFLECT_VALUE(QuestObjectiveCompleted) + MH_ENUM_REFLECT_VALUE(SPHapWeapEvent) + MH_ENUM_REFLECT_VALUE(HapDmg) + MH_ENUM_REFLECT_VALUE(HapPunch) + MH_ENUM_REFLECT_VALUE(HapSetDrag) + MH_ENUM_REFLECT_VALUE(HapSetConst) + MH_ENUM_REFLECT_VALUE(HapMeleeContact) +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector/Log.h b/tf2_bot_detector/Log.h index 1302f6d9..dc7a2965 100644 --- a/tf2_bot_detector/Log.h +++ b/tf2_bot_detector/Log.h @@ -97,14 +97,16 @@ namespace tf2_bot_detector namespace detail::log_h { template 0)>> - NOINLINE inline void LogImpl(const LogMessageColor& color, LogSeverity severity, LogVisibility visibility, - const std::string_view& fmtStr, const TArgs&... args) + NOINLINE inline auto LogImpl(const LogMessageColor& color, LogSeverity severity, LogVisibility visibility, + const std::string_view& fmtStr, const TArgs&... args) -> + decltype(mh::try_format(fmtStr, args...), void()) { ILogManager::GetInstance().Log(mh::try_format(fmtStr, args...), color, severity, visibility); } template - NOINLINE inline void LogImpl(const LogMessageColor& color, LogSeverity severity, LogVisibility visibility, - const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) + NOINLINE inline auto LogImpl(const LogMessageColor& color, LogSeverity severity, LogVisibility visibility, + const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) -> + decltype(mh::try_format(fmtStr, args...), void()) { using namespace std::string_view_literals; if (!fmtStr.empty()) @@ -117,7 +119,8 @@ namespace tf2_bot_detector #undef LOG_DEFINITION_HELPER #define LOG_DEFINITION_HELPER(name, defaultColor, severity, visibility) \ template 0)>> \ - inline void name(const LogMessageColor& color, const std::string_view& fmtStr, const TArgs&... args) \ + inline auto name(const LogMessageColor& color, const std::string_view& fmtStr, const TArgs&... args) -> \ + decltype(detail::log_h::LogImpl(color, (severity), (visibility), fmtStr, args...)) \ { \ detail::log_h::LogImpl(color, (severity), (visibility), fmtStr, args...); \ } \ @@ -126,7 +129,8 @@ namespace tf2_bot_detector ILogManager::GetInstance().Log(std::move(msg), color, (severity), (visibility)); \ } \ template 0)>> \ - inline void name(const std::string_view& fmtStr, const TArgs&... args) \ + inline auto name(const std::string_view& fmtStr, const TArgs&... args) -> \ + decltype(name((defaultColor), fmtStr, args...)) \ { \ name((defaultColor), fmtStr, args...); \ } \ @@ -136,12 +140,14 @@ namespace tf2_bot_detector } \ \ template \ - inline void name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ + inline auto name(const LogMessageColor& color, const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) -> \ + decltype(detail::log_h::LogImpl(color, (severity), (visibility), location, fmtStr, args...)) \ { \ detail::log_h::LogImpl(color, (severity), (visibility), location, fmtStr, args...); \ } \ template \ - inline void name(const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) \ + inline auto name(const mh::source_location& location, const std::string_view& fmtStr, const TArgs&... args) -> \ + decltype(name((defaultColor), location, fmtStr, args...)) \ { \ name((defaultColor), location, fmtStr, args...); \ } \ diff --git a/tf2_bot_detector/ModeratorLogic.cpp b/tf2_bot_detector/ModeratorLogic.cpp index daad3f3d..06d6b875 100644 --- a/tf2_bot_detector/ModeratorLogic.cpp +++ b/tf2_bot_detector/ModeratorLogic.cpp @@ -168,12 +168,12 @@ void ModeratorLogic::OnRuleMatch(const ModerationRule& rule, const IPlayer& play for (PlayerAttribute attribute : rule.m_Actions.m_Mark) { if (SetPlayerAttribute(player, attribute)) - Log("Marked {} with {} due to rule match with {}", player, attribute, std::quoted(rule.m_Description)); + Log("Marked {} with {:v} due to rule match with {}", player, mh::enum_fmt(attribute), std::quoted(rule.m_Description)); } for (PlayerAttribute attribute : rule.m_Actions.m_Unmark) { if (SetPlayerAttribute(player, attribute, false)) - Log("Unmarked {} with {} due to rule match with {}", player, attribute, std::quoted(rule.m_Description)); + Log("Unmarked {} with {:v} due to rule match with {}", player, mh::enum_fmt(attribute), std::quoted(rule.m_Description)); } } diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index e86f3a39..f85cf149 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -166,8 +166,8 @@ namespace break; default: - LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown UpdateStatus {}", updateStatus); - ImGui::TextFmt({ 1, 0, 0, 1 }, "Unexpected({})", updateStatus); + LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown UpdateStatus {}", mh::enum_fmt(updateStatus)); + ImGui::TextFmt({ 1, 0, 0, 1 }, "Unexpected({})", mh::enum_fmt(updateStatus)); break; } diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.h b/tf2_bot_detector/UI/ImGui_TF2BotDetector.h index 1d907a46..e1a7a798 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.h +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.h @@ -17,7 +17,8 @@ namespace ImGui { template - inline void TextFmt(const std::string_view& fmtStr, const TArgs&... args) + inline auto TextFmt(const std::string_view& fmtStr, const TArgs&... args) -> + decltype(mh::format(fmtStr, args...), void()) { if constexpr (sizeof...(TArgs) > 0) return TextFmt(mh::fmtstr<3073>(fmtStr, args...)); @@ -25,7 +26,8 @@ namespace ImGui return TextUnformatted(fmtStr.data(), fmtStr.data() + fmtStr.size()); } template - inline void TextFmt(const ImVec4& color, const std::string_view& fmtStr, const TArgs&... args) + inline auto TextFmt(const ImVec4& color, const std::string_view& fmtStr, const TArgs&... args) -> + decltype(TextFmt(fmtStr, args...)) { ImGuiDesktop::TextColor scope(color); TextFmt(fmtStr, args...); diff --git a/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp b/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp index 6a92e81f..b6d5057b 100644 --- a/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp +++ b/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp @@ -439,12 +439,13 @@ void MainWindow::OnDrawScoreboardContextMenu(IPlayer& player) { for (int i = 0; i < (int)PlayerAttribute::COUNT; i++) { - const bool existingMarked = modLogic.HasPlayerAttributes(player, PlayerAttribute(i)); + const auto attr = PlayerAttribute(i); + const bool existingMarked = modLogic.HasPlayerAttributes(player, attr); - if (ImGui::MenuItem(mh::fmtstr<512>("{}", PlayerAttribute(i)).c_str(), nullptr, existingMarked)) + if (ImGui::MenuItem(mh::fmtstr<512>("{}", mh::enum_fmt(attr)).c_str(), nullptr, existingMarked)) { - if (modLogic.SetPlayerAttribute(player, PlayerAttribute(i), !existingMarked)) - Log("Manually marked {}{} {}", player, (existingMarked ? " NOT" : ""), PlayerAttribute(i)); + if (modLogic.SetPlayerAttribute(player, attr, !existingMarked)) + Log("Manually marked {}{} {}", player, (existingMarked ? " NOT" : ""), mh::enum_fmt(attr)); } } From 0291687750c60af9a896b302934ca8744f0e57d3 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 19:31:58 -0700 Subject: [PATCH 145/161] Fixed compile errors in the updater caused by the previous commit. --- tf2_bot_detector_updater/main.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tf2_bot_detector_updater/main.cpp b/tf2_bot_detector_updater/main.cpp index c9cef34b..80f56938 100644 --- a/tf2_bot_detector_updater/main.cpp +++ b/tf2_bot_detector_updater/main.cpp @@ -43,7 +43,10 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; if (s_CmdLineArgs.m_ReleaseChannel == ReleaseChannel::None) { std::cerr << mh::format("Release channel not specified. Use --release-channel <{:v}|{:v}|{:v}>", - ReleaseChannel::Public, ReleaseChannel::Preview, ReleaseChannel::Nightly) << std::endl; + mh::enum_fmt(ReleaseChannel::Public), + mh::enum_fmt(ReleaseChannel::Preview), + mh::enum_fmt(ReleaseChannel::Nightly)) + << std::endl; return 1; } } @@ -148,7 +151,7 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; return Update_Portable(); default: - std::cerr << mh::format("Unhandled UpdateType {}", s_CmdLineArgs.m_UpdateType) << std::endl; + std::cerr << mh::format("Unhandled UpdateType {}", mh::enum_fmt(s_CmdLineArgs.m_UpdateType)) << std::endl; return 1; } } From 2ba94936de04947b4ccf57e3b011d2a9ef5e2d76 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Sun, 6 Sep 2020 19:55:47 -0700 Subject: [PATCH 146/161] * Fixed discord not being compiled into the project properly. * Fixed compile errors related to formatting in the discord integration code. --- CMakeLists.txt | 6 +- submodules/mh_stuff | 2 +- tf2_bot_detector/DiscordRichPresence.cpp | 175 ++++++++++------------- 3 files changed, 80 insertions(+), 103 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c74dfb09..282cf47b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ cmake_minimum_required(VERSION 3.17) +option(TF2BD_ENABLE_DISCORD_INTEGRATION "Enable discord integration" on) +option(TF2BD_ENABLE_TESTS "Enable test compilation" off) + include(cmake/init-preproject.cmake) project(tf2_bot_detector) include(cmake/init-postproject.cmake) @@ -14,6 +17,3 @@ add_subdirectory(tf2_bot_detector) set(ENABLE_EXAMPLES off CACHE BOOL "Build examples" FORCE) add_subdirectory("submodules/cppcoro") - -option(TF2BD_ENABLE_DISCORD_INTEGRATION "Enable discord integration" on) -option(TF2BD_ENABLE_TESTS "Enable test compilation" off) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index 2c3f4228..8d7db5ff 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit 2c3f4228ca7a68baef3708175f7220bfb3cd1647 +Subproject commit 8d7db5ff6fcb12a4764adec5854114c88feabb87 diff --git a/tf2_bot_detector/DiscordRichPresence.cpp b/tf2_bot_detector/DiscordRichPresence.cpp index 12077cfb..4e495d13 100644 --- a/tf2_bot_detector/DiscordRichPresence.cpp +++ b/tf2_bot_detector/DiscordRichPresence.cpp @@ -77,22 +77,28 @@ static constexpr LogMessageColor DISCORD_LOG_COLOR{ 117 / 255.0f, 136 / 255.0f, static bool s_DiscordDebugLogEnabled = true; -static void DiscordDebugLog(const std::string_view& msg) +template +static auto DiscordDebugLog(const std::string_view& fmtStr, const TArgs&... args) -> + decltype(mh::format(fmtStr, args...), void()) { if (!s_DiscordDebugLogEnabled) return; - DebugLog(DISCORD_LOG_COLOR, "DRP: {}", msg); + DebugLog(DISCORD_LOG_COLOR, "DRP: {}", mh::format(fmtStr, args...)); } -static void DiscordDebugLog(const mh::source_location& location, const std::string_view& msg = {}) + +template +static auto DiscordDebugLog(const mh::source_location& location, + const std::string_view& fmtStr = {}, const TArgs&... args) -> + decltype(mh::format(fmtStr, args...), void()) { if (!s_DiscordDebugLogEnabled) return; - if (msg.empty()) + if (fmtStr.empty()) DebugLog(DISCORD_LOG_COLOR, location); else - DebugLog(DISCORD_LOG_COLOR, location, "DRP: {}", msg); + DebugLog(DISCORD_LOG_COLOR, location, "DRP: {}", mh::format(fmtStr, args...)); } namespace @@ -105,94 +111,65 @@ namespace }; } -#undef OS_CASE -#define OS_CASE(v) case v : return os << #v -template -static std::basic_ostream& operator<<(std::basic_ostream& os, discord::Result result) -{ - using Result = discord::Result; - - switch (result) - { - OS_CASE(Result::Ok); - OS_CASE(Result::ServiceUnavailable); - OS_CASE(Result::InvalidVersion); - OS_CASE(Result::LockFailed); - OS_CASE(Result::InternalError); - OS_CASE(Result::InvalidPayload); - OS_CASE(Result::InvalidCommand); - OS_CASE(Result::InvalidPermissions); - OS_CASE(Result::NotFetched); - OS_CASE(Result::NotFound); - OS_CASE(Result::Conflict); - OS_CASE(Result::InvalidSecret); - OS_CASE(Result::InvalidJoinSecret); - OS_CASE(Result::NoEligibleActivity); - OS_CASE(Result::InvalidInvite); - OS_CASE(Result::NotAuthenticated); - OS_CASE(Result::InvalidAccessToken); - OS_CASE(Result::ApplicationMismatch); - OS_CASE(Result::InvalidDataUrl); - OS_CASE(Result::InvalidBase64); - OS_CASE(Result::NotFiltered); - OS_CASE(Result::LobbyFull); - OS_CASE(Result::InvalidLobbySecret); - OS_CASE(Result::InvalidFilename); - OS_CASE(Result::InvalidFileSize); - OS_CASE(Result::InvalidEntitlement); - OS_CASE(Result::NotInstalled); - OS_CASE(Result::NotRunning); - OS_CASE(Result::InsufficientBuffer); - OS_CASE(Result::PurchaseCanceled); - OS_CASE(Result::InvalidGuild); - OS_CASE(Result::InvalidEvent); - OS_CASE(Result::InvalidChannel); - OS_CASE(Result::InvalidOrigin); - OS_CASE(Result::RateLimited); - OS_CASE(Result::OAuth2Error); - OS_CASE(Result::SelectChannelTimeout); - OS_CASE(Result::GetGuildTimeout); - OS_CASE(Result::SelectVoiceForceRequired); - OS_CASE(Result::CaptureShortcutAlreadyListening); - OS_CASE(Result::UnauthorizedForAchievement); - OS_CASE(Result::InvalidGiftCode); - OS_CASE(Result::PurchaseError); - OS_CASE(Result::TransactionAborted); - - default: - return os << "Result(" << +std::underlying_type_t(result) << ')'; - } -} -template -static std::basic_ostream& operator<<(std::basic_ostream& os, discord::ActivityType type) -{ - using ActivityType = discord::ActivityType; - switch (type) - { - OS_CASE(ActivityType::Playing); - OS_CASE(ActivityType::Streaming); - OS_CASE(ActivityType::Listening); - OS_CASE(ActivityType::Watching); - - default: - return os << "ActivityType(" << +std::underlying_type_t(type) << ')'; - } -} - -template -static std::basic_ostream& operator<<(std::basic_ostream& os, ConnectionState cs) -{ - switch (cs) - { - OS_CASE(ConnectionState::Disconnected); - OS_CASE(ConnectionState::Local); - OS_CASE(ConnectionState::Nonlocal); - - default: - return os << "ConnectionState(" << +std::underlying_type_t(cs) << ')'; - } -} -#undef OS_CASE +MH_ENUM_REFLECT_BEGIN(discord::Result) + MH_ENUM_REFLECT_VALUE(Ok) + MH_ENUM_REFLECT_VALUE(ServiceUnavailable) + MH_ENUM_REFLECT_VALUE(InvalidVersion) + MH_ENUM_REFLECT_VALUE(LockFailed) + MH_ENUM_REFLECT_VALUE(InternalError) + MH_ENUM_REFLECT_VALUE(InvalidPayload) + MH_ENUM_REFLECT_VALUE(InvalidCommand) + MH_ENUM_REFLECT_VALUE(InvalidPermissions) + MH_ENUM_REFLECT_VALUE(NotFetched) + MH_ENUM_REFLECT_VALUE(NotFound) + MH_ENUM_REFLECT_VALUE(Conflict) + MH_ENUM_REFLECT_VALUE(InvalidSecret) + MH_ENUM_REFLECT_VALUE(InvalidJoinSecret) + MH_ENUM_REFLECT_VALUE(NoEligibleActivity) + MH_ENUM_REFLECT_VALUE(InvalidInvite) + MH_ENUM_REFLECT_VALUE(NotAuthenticated) + MH_ENUM_REFLECT_VALUE(InvalidAccessToken) + MH_ENUM_REFLECT_VALUE(ApplicationMismatch) + MH_ENUM_REFLECT_VALUE(InvalidDataUrl) + MH_ENUM_REFLECT_VALUE(InvalidBase64) + MH_ENUM_REFLECT_VALUE(NotFiltered) + MH_ENUM_REFLECT_VALUE(LobbyFull) + MH_ENUM_REFLECT_VALUE(InvalidLobbySecret) + MH_ENUM_REFLECT_VALUE(InvalidFilename) + MH_ENUM_REFLECT_VALUE(InvalidFileSize) + MH_ENUM_REFLECT_VALUE(InvalidEntitlement) + MH_ENUM_REFLECT_VALUE(NotInstalled) + MH_ENUM_REFLECT_VALUE(NotRunning) + MH_ENUM_REFLECT_VALUE(InsufficientBuffer) + MH_ENUM_REFLECT_VALUE(PurchaseCanceled) + MH_ENUM_REFLECT_VALUE(InvalidGuild) + MH_ENUM_REFLECT_VALUE(InvalidEvent) + MH_ENUM_REFLECT_VALUE(InvalidChannel) + MH_ENUM_REFLECT_VALUE(InvalidOrigin) + MH_ENUM_REFLECT_VALUE(RateLimited) + MH_ENUM_REFLECT_VALUE(OAuth2Error) + MH_ENUM_REFLECT_VALUE(SelectChannelTimeout) + MH_ENUM_REFLECT_VALUE(GetGuildTimeout) + MH_ENUM_REFLECT_VALUE(SelectVoiceForceRequired) + MH_ENUM_REFLECT_VALUE(CaptureShortcutAlreadyListening) + MH_ENUM_REFLECT_VALUE(UnauthorizedForAchievement) + MH_ENUM_REFLECT_VALUE(InvalidGiftCode) + MH_ENUM_REFLECT_VALUE(PurchaseError) + MH_ENUM_REFLECT_VALUE(TransactionAborted) +MH_ENUM_REFLECT_END() + +MH_ENUM_REFLECT_BEGIN(discord::ActivityType) + MH_ENUM_REFLECT_VALUE(Playing) + MH_ENUM_REFLECT_VALUE(Streaming) + MH_ENUM_REFLECT_VALUE(Listening) + MH_ENUM_REFLECT_VALUE(Watching) +MH_ENUM_REFLECT_END() + +MH_ENUM_REFLECT_BEGIN(ConnectionState) + MH_ENUM_REFLECT_VALUE(Disconnected) + MH_ENUM_REFLECT_VALUE(Local) + MH_ENUM_REFLECT_VALUE(Nonlocal) +MH_ENUM_REFLECT_END() static std::strong_ordering strcmp3(const char* lhs, const char* rhs) { @@ -406,7 +383,7 @@ namespace bool OnChange(const ConnectionState& newValue) const override { - DiscordDebugLog("ConnectionState "s << get() << " -> " << newValue); + DiscordDebugLog(mh::format("ConnectionState {} -> {}", mh::enum_fmt(get()), mh::enum_fmt(newValue))); return true; } @@ -886,14 +863,14 @@ void DiscordState::Update() if (auto result = discord::Core::Create(730945386390224976, DiscordCreateFlags_NoRequireDiscord, &core); result != discord::Result::Ok) { - LogError("Failed to initialize Discord Game SDK: "s << result); + LogError("Failed to initialize Discord Game SDK: {}", mh::enum_fmt(result)); } else { m_Core.reset(core); if (auto result = m_Core->ActivityManager().RegisterSteam(440); result != discord::Result::Ok) - LogError("Failed to register discord integration as steam appid 440: "s << result); + LogError("Failed to register discord integration as steam appid 440: {}", mh::enum_fmt(result)); } } @@ -915,12 +892,12 @@ void DiscordState::Update() { if (result == discord::Result::Ok) { - DiscordDebugLog("Updated discord activity state: "s << nextActivity); + DiscordDebugLog("Updated discord activity state: {}", nextActivity); } else { LogWarning(MH_SOURCE_LOCATION_CURRENT(), - "Failed to update discord activity state: "s << result); + "Failed to update discord activity state: {}", mh::enum_fmt(result)); } }); @@ -939,7 +916,7 @@ void DiscordState::Update() // Run discord callbacks if (auto result = m_Core->RunCallbacks(); result != discord::Result::Ok) { - auto errMsg = mh::format("Failed to run discord callbacks: {}", result); + auto errMsg = mh::format("Failed to run discord callbacks: {}", mh::enum_fmt(result)); switch (result) { case discord::Result::NotRunning: From 9b70ab8a97ed069392c3386c0781f1befbbb9409 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 13:04:42 -0700 Subject: [PATCH 147/161] Don't cache vcpkg/downloads/tools after all, it makes the cache file massive which results in a net increase in setup time --- .github/workflows/ccpp.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 53a49876..8c3e28cb 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -97,11 +97,10 @@ jobs: env: VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('vcpkg.json') }}-${{ matrix.triplet }}-2 + key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('vcpkg.json') }}-${{ matrix.triplet }}-3 path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe - ${{ env.VCPKG_ROOT }}/downloads/tools - uses: seanmiddleditch/gha-setup-ninja@v2 - name: Configure build tools @@ -238,11 +237,10 @@ jobs: env: VCPKG_ROOT: '${{ github.workspace }}/submodules/vcpkg' with: - key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('tf2_bot_detector_updater/vcpkg.json') }}-${{ matrix.triplet }}-2 + key: ${{ hashFiles('.git/modules/submodules/vcpkg/HEAD') }}-${{ hashFiles('tf2_bot_detector_updater/vcpkg.json') }}-${{ matrix.triplet }}-3 path: | ${{ env.VCPKG_ROOT }}/archives ${{ env.VCPKG_ROOT }}/vcpkg.exe - ${{ env.VCPKG_ROOT }}/downloads/tools - uses: seanmiddleditch/gha-setup-ninja@v2 - name: Configure build tools From 055c48673c58f582bfb0412f526cc230bc1e1e13 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 14:22:21 -0700 Subject: [PATCH 148/161] Fixed the project being built at a very low warning level. --- CMakeLists.txt | 8 +++-- cmake/init-postproject.cmake | 21 ++++++++++++-- cmake/warning_level.cmake | 16 ++++++++++ submodules/imgui_desktop | 2 +- submodules/mh_stuff | 2 +- tf2_bot_detector/CMakeLists.txt | 3 +- tf2_bot_detector/Config/ChatWrappers.cpp | 12 +++++--- tf2_bot_detector/Config/ChatWrappers.h | 29 +++++++------------ tf2_bot_detector/ConsoleLog/ConsoleLines.cpp | 20 ++++++------- tf2_bot_detector/ConsoleLog/NetworkStatus.cpp | 1 + tf2_bot_detector/DiscordRichPresence.cpp | 6 ++++ .../Networking/NetworkHelpers.cpp | 2 ++ .../Platform/Windows/PlatformInstall.cpp | 2 +- tf2_bot_detector/Platform/Windows/Windows.cpp | 6 ++-- .../SetupFlow/UpdateCheckPage.cpp | 13 +++++++-- tf2_bot_detector/SteamID.h | 25 +++++++++------- tf2_bot_detector/UI/MainWindow.Scoreboard.cpp | 1 + tf2_bot_detector/UI/MainWindow.cpp | 2 ++ tf2_bot_detector/UpdateManager.h | 3 ++ tf2_bot_detector/Util/TextUtils.cpp | 15 ++++------ tf2_bot_detector/Version.cpp | 2 +- tf2_bot_detector/WorldState.cpp | 5 ++++ 22 files changed, 126 insertions(+), 70 deletions(-) create mode 100644 cmake/warning_level.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 282cf47b..ab286524 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,13 +7,17 @@ include(cmake/init-preproject.cmake) project(tf2_bot_detector) include(cmake/init-postproject.cmake) +set_warning_level(1) +set(ENABLE_EXAMPLES off CACHE BOOL "Build examples" FORCE) +add_subdirectory("submodules/cppcoro") + +set_warning_level(3) add_subdirectory(submodules/ValveFileVDF) add_subdirectory(submodules/SourceRCON) add_subdirectory(submodules/imgui_desktop) add_subdirectory(submodules/mh_stuff) + add_subdirectory(tf2_bot_detector_common) # add_subdirectory(tf2_bot_detector_updater) add_subdirectory(tf2_bot_detector) -set(ENABLE_EXAMPLES off CACHE BOOL "Build examples" FORCE) -add_subdirectory("submodules/cppcoro") diff --git a/cmake/init-postproject.cmake b/cmake/init-postproject.cmake index 549825f0..8a31ec87 100644 --- a/cmake/init-postproject.cmake +++ b/cmake/init-postproject.cmake @@ -1,3 +1,10 @@ +include_guard(GLOBAL) + +message("CMAKE_CURRENT_LIST_DIR = ${CMAKE_CURRENT_LIST_DIR}") +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) + +include(warning_level) + message("TF2BD CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") if ("${TF2BD_VERSION_BUILD}" STREQUAL "") @@ -60,7 +67,15 @@ if (MSVC) endif() # TODO: Find a way to do this locally - if(MSVC) - add_compile_options(/WX) - endif() + add_compile_options( + /WX + /experimental:external # enable /external command line switches, see https://devblogs.microsoft.com/cppblog/broken-warnings-theory/ + /external:W1 # Warning level 1 on external headers + /external:anglebrackets # Consider anything with #include to be "external" + + # /w34061 # enumerator 'identifier' in a switch of enum 'enumeration' is not explicitly handled by a case label + /w34062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled + /w44103 # 'filename' : alignment changed after including header, may be due to missing #pragma pack(pop) + # Windows headers trigger this + ) endif() diff --git a/cmake/warning_level.cmake b/cmake/warning_level.cmake new file mode 100644 index 00000000..5eaac705 --- /dev/null +++ b/cmake/warning_level.cmake @@ -0,0 +1,16 @@ + +function(set_warning_level level) + + get_property(TEMP_COMPILE_OPTIONS DIRECTORY PROPERTY COMPILE_OPTIONS) + # message("COMPILE_OPTIONS pre-remove = ${TEMP_COMPILE_OPTIONS}") + + if (MSVC) + list(FILTER TEMP_COMPILE_OPTIONS EXCLUDE REGEX "/W[0-9]") + # message("COMPILE_OPTIONS post-remove = ${TEMP_COMPILE_OPTIONS}") + list(APPEND TEMP_COMPILE_OPTIONS "/W${level}") + endif() + + # message("COMPILE_OPTIONS post-add = ${TEMP_COMPILE_OPTIONS}") + set_property(DIRECTORY PROPERTY COMPILE_OPTIONS "${TEMP_COMPILE_OPTIONS}") + +endfunction() diff --git a/submodules/imgui_desktop b/submodules/imgui_desktop index 94f01e55..a5da2861 160000 --- a/submodules/imgui_desktop +++ b/submodules/imgui_desktop @@ -1 +1 @@ -Subproject commit 94f01e555904549111c72d31b6c5a54f9eed8f7d +Subproject commit a5da2861c14fdcda7b9b0ea924057ba19ff70f44 diff --git a/submodules/mh_stuff b/submodules/mh_stuff index 8d7db5ff..79348bf0 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit 8d7db5ff6fcb12a4764adec5854114c88feabb87 +Subproject commit 79348bf042562741fef4560e71fd275200d96071 diff --git a/tf2_bot_detector/CMakeLists.txt b/tf2_bot_detector/CMakeLists.txt index c6d2bab0..5fd39155 100644 --- a/tf2_bot_detector/CMakeLists.txt +++ b/tf2_bot_detector/CMakeLists.txt @@ -166,7 +166,8 @@ if(WIN32) "Resources.rc" "Platform/Windows/Windows.cpp" "Platform/Windows/PlatformInstall.cpp" - "Platform/Windows/Platform.cpp") + "Platform/Windows/Platform.cpp" + ) endif() if (TF2BD_ENABLE_DISCORD_INTEGRATION) diff --git a/tf2_bot_detector/Config/ChatWrappers.cpp b/tf2_bot_detector/Config/ChatWrappers.cpp index 137f8f67..f68ce667 100644 --- a/tf2_bot_detector/Config/ChatWrappers.cpp +++ b/tf2_bot_detector/Config/ChatWrappers.cpp @@ -379,9 +379,11 @@ static constexpr std::string_view GetChatCategoryKey(ChatCategory cat, bool isEn case ChatCategory::AllDead: return "[english]TF_Chat_AllDead"sv; case ChatCategory::Team: return "[english]TF_Chat_Team"sv; case ChatCategory::TeamDead: return "[english]TF_Chat_Team_Dead"sv; - case ChatCategory::Spec: return "[english]TF_Chat_AllSpec"sv; + case ChatCategory::Spec: return "[english]TF_Chat_AllSpec"sv; case ChatCategory::SpecTeam: return "[english]TF_Chat_Spec"sv; case ChatCategory::Coach: return "[english]TF_Chat_Coach"sv; + + case ChatCategory::COUNT: break; } } else @@ -392,13 +394,15 @@ static constexpr std::string_view GetChatCategoryKey(ChatCategory cat, bool isEn case ChatCategory::AllDead: return "TF_Chat_AllDead"sv; case ChatCategory::Team: return "TF_Chat_Team"sv; case ChatCategory::TeamDead: return "TF_Chat_Team_Dead"sv; - case ChatCategory::Spec: return "TF_Chat_AllSpec"sv; + case ChatCategory::Spec: return "TF_Chat_AllSpec"sv; case ChatCategory::SpecTeam: return "TF_Chat_Spec"sv; case ChatCategory::Coach: return "TF_Chat_Coach"sv; + + case ChatCategory::COUNT: break; } } - LogError(std::string(__FUNCTION__ ": Unknown key for ") << cat << " with isEnglish = " << isEnglish); + LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown key for {} with isEnglish = {}", mh::enum_fmt(cat), isEnglish); return "TF_Chat_UNKNOWN"sv; } @@ -632,7 +636,7 @@ ChatWrappers tf2_bot_detector::RandomizeChatWrappers(const std::filesystem::path std::filesystem::create_directories(outputDir); if (progress) - progress->m_MaxValue = std::size(LANGUAGES) * 5; + progress->m_MaxValue = unsigned(std::size(LANGUAGES) * 5); const auto IncrementProgress = [&] { diff --git a/tf2_bot_detector/Config/ChatWrappers.h b/tf2_bot_detector/Config/ChatWrappers.h index dd783d4e..05acdd76 100644 --- a/tf2_bot_detector/Config/ChatWrappers.h +++ b/tf2_bot_detector/Config/ChatWrappers.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -121,22 +122,12 @@ namespace tf2_bot_detector ChatWrappersProgress* progress = nullptr); } -template -std::basic_ostream& operator<<(std::basic_ostream& os, tf2_bot_detector::ChatCategory cat) -{ - using tf2_bot_detector::ChatCategory; - switch (cat) - { - case ChatCategory::All: return os << "ChatCategory::All"; - case ChatCategory::AllDead: return os << "ChatCategory::AllDead"; - case ChatCategory::Team: return os << "ChatCategory::Team"; - case ChatCategory::TeamDead: return os << "ChatCategory::TeamDead"; - case ChatCategory::Spec: return os << "ChatCategory::Spec"; - case ChatCategory::SpecTeam: return os << "ChatCategory::SpecTeam"; - case ChatCategory::Coach: return os << "ChatCategory::Coach"; - - default: - assert(!"Unknown ChatCategory"); - return os << "ChatCategory(" << +std::underlying_type_t(cat) << ')'; - } -} +MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::ChatCategory) + MH_ENUM_REFLECT_VALUE(All) + MH_ENUM_REFLECT_VALUE(AllDead) + MH_ENUM_REFLECT_VALUE(Team) + MH_ENUM_REFLECT_VALUE(TeamDead) + MH_ENUM_REFLECT_VALUE(Spec) + MH_ENUM_REFLECT_VALUE(SpecTeam) + MH_ENUM_REFLECT_VALUE(Coach) +MH_ENUM_REFLECT_END() diff --git a/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp b/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp index 07c49c8a..7a52684d 100644 --- a/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp +++ b/tf2_bot_detector/ConsoleLog/ConsoleLines.cpp @@ -7,6 +7,7 @@ #include "Log.h" #include "WorldState.h" +#include #include #include #include @@ -651,18 +652,15 @@ std::shared_ptr SVCUserMessageLine::TryParse(const std::string_vie bool SVCUserMessageLine::IsSpecial(UserMessageType type) { #ifdef _DEBUG - switch (type) - { - case UserMessageType::CallVoteFailed: - case UserMessageType::VoteFailed: - case UserMessageType::VotePass: - case UserMessageType::VoteSetup: - case UserMessageType::VoteStart: - return true; - } -#endif - + return mh::any_eq(type, + UserMessageType::CallVoteFailed, + UserMessageType::VoteFailed, + UserMessageType::VotePass, + UserMessageType::VoteSetup, + UserMessageType::VoteStart); +#else return false; +#endif } bool SVCUserMessageLine::ShouldPrint() const diff --git a/tf2_bot_detector/ConsoleLog/NetworkStatus.cpp b/tf2_bot_detector/ConsoleLog/NetworkStatus.cpp index 414d99ac..3dc39c1d 100644 --- a/tf2_bot_detector/ConsoleLog/NetworkStatus.cpp +++ b/tf2_bot_detector/ConsoleLog/NetworkStatus.cpp @@ -71,6 +71,7 @@ void SplitPacketLine::Print(const PrintArgs& args) const case SocketType::SystemLink: socketType = "lnk"; break; case SocketType::LAN: socketType = "lan"; break; + case SocketType::COUNT: default: throw std::runtime_error("Invalid SocketType"); } diff --git a/tf2_bot_detector/DiscordRichPresence.cpp b/tf2_bot_detector/DiscordRichPresence.cpp index 4e495d13..4bf44443 100644 --- a/tf2_bot_detector/DiscordRichPresence.cpp +++ b/tf2_bot_detector/DiscordRichPresence.cpp @@ -561,6 +561,9 @@ discord::Activity DiscordGameState::ConstructActivity() const assets.SetSmallImage("leaderboard_class_spy"); assets.SetSmallText("Spy"); break; + + case TFClassType::Undefined: + break; } } @@ -817,6 +820,9 @@ void DiscordState::OnConsoleLineParsed(IWorldState& world, IConsoleLine& line) m_GameState.OnServerIPUpdate(umsgLine.GetAddress()); break; } + + default: + break; } } diff --git a/tf2_bot_detector/Networking/NetworkHelpers.cpp b/tf2_bot_detector/Networking/NetworkHelpers.cpp index 8c3adc27..8512525a 100644 --- a/tf2_bot_detector/Networking/NetworkHelpers.cpp +++ b/tf2_bot_detector/Networking/NetworkHelpers.cpp @@ -1,3 +1,5 @@ +#define _WINSOCK_DEPRECATED_NO_WARNINGS + #include "NetworkHelpers.h" #include "Log.h" diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index 26a700e4..b9d9918d 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -35,7 +35,7 @@ static winrt::Windows::ApplicationModel::Package GetAppPackage() return package; } - catch (const winrt::hresult_error& e) + catch (const winrt::hresult_error&) { //DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Unable to confirm we are an installed package: {}", ToMB(e.message())); return nullptr; diff --git a/tf2_bot_detector/Platform/Windows/Windows.cpp b/tf2_bot_detector/Platform/Windows/Windows.cpp index 77d65c0e..66a875b0 100644 --- a/tf2_bot_detector/Platform/Windows/Windows.cpp +++ b/tf2_bot_detector/Platform/Windows/Windows.cpp @@ -16,7 +16,7 @@ using namespace std::string_view_literals; std::filesystem::path tf2_bot_detector::Platform::GetCurrentExeDir() { WCHAR path[32768]; - const auto length = GetModuleFileNameW(nullptr, path, std::size(path)); + const auto length = GetModuleFileNameW(nullptr, path, (DWORD)std::size(path)); const auto error = GetLastError(); if (error != ERROR_SUCCESS) @@ -33,7 +33,7 @@ namespace tf2_bot_detector static std::wstring GetCurrentPackageFamilyName() { WCHAR name[PACKAGE_FAMILY_NAME_MAX_LENGTH + 1]; - UINT32 nameLength = std::size(name); + UINT32 nameLength = UINT32(std::size(name)); const auto errc = ::GetCurrentPackageFamilyName(&nameLength, name); switch (errc) @@ -52,7 +52,7 @@ namespace tf2_bot_detector static std::filesystem::path GetCurrentPackagePath() { WCHAR path[32768]; - UINT32 pathLength = std::size(path); + UINT32 pathLength = UINT32(std::size(path)); const auto errc = ::GetCurrentPackagePath(&pathLength, path); switch (errc) diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index f85cf149..be7bfc28 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -165,10 +165,17 @@ namespace ImGui::TextFmt({ 0, 1, 0, 1 }, "Update succeeded. Restart TF2 Bot Detector to apply."); break; - default: - LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown UpdateStatus {}", mh::enum_fmt(updateStatus)); - ImGui::TextFmt({ 1, 0, 0, 1 }, "Unexpected({})", mh::enum_fmt(updateStatus)); + case UpdateStatus::UpdateToolDownloading: + ImGui::TextFmt("Downloading updater..."); break; + case UpdateStatus::UpdateToolDownloadingFailed: + ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update tool."); + break; + + //default: + // LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown UpdateStatus {}", mh::enum_fmt(updateStatus)); + // ImGui::TextFmt({ 1, 0, 0, 1 }, "Unexpected({})", mh::enum_fmt(updateStatus)); + // break; } if (continueButtonMode == ContinueButtonMode::ContinueWithoutUpdating && diff --git a/tf2_bot_detector/SteamID.h b/tf2_bot_detector/SteamID.h index edb76e7c..a248cb2e 100644 --- a/tf2_bot_detector/SteamID.h +++ b/tf2_bot_detector/SteamID.h @@ -110,22 +110,25 @@ std::basic_ostream& operator<<(std::basic_ostream& { using namespace tf2_bot_detector; - char c; + const char* c; switch (id.Type) { - case SteamAccountType::Individual: c = 'U'; break; - case SteamAccountType::Multiseat: c = 'M'; break; - case SteamAccountType::GameServer: c = 'G'; break; - case SteamAccountType::AnonGameServer: c = 'A'; break; - case SteamAccountType::Pending: c = 'P'; break; - case SteamAccountType::ContentServer: c = 'C'; break; - case SteamAccountType::Clan: c = 'g'; break; - case SteamAccountType::AnonUser: c = 'a'; break; - case SteamAccountType::Chat: c = 'c'; break; + case SteamAccountType::P2PSuperSeeder: c = "P2PSuperSeeder"; break; + + case SteamAccountType::Individual: c = "U"; break; + case SteamAccountType::Multiseat: c = "M"; break; + case SteamAccountType::GameServer: c = "G"; break; + case SteamAccountType::AnonGameServer: c = "A"; break; + case SteamAccountType::Pending: c = "P"; break; + case SteamAccountType::ContentServer: c = "C"; break; + case SteamAccountType::Clan: c = "g"; break; + case SteamAccountType::AnonUser: c = "a"; break; + case SteamAccountType::Chat: c = "c"; break; default: assert(!"Invalid value when serializing SteamID"); - case SteamAccountType::Invalid: c = 'I'; break; + [[fallthrough]]; + case SteamAccountType::Invalid: c = "I"; break; } return os << '[' << c << ':' << static_cast(id.Universe) << ':' << id.ID << ']'; diff --git a/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp b/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp index b6d5057b..6a05ad23 100644 --- a/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp +++ b/tf2_bot_detector/UI/MainWindow.Scoreboard.cpp @@ -182,6 +182,7 @@ void MainWindow::OnDrawScoreboardRow(IPlayer& player) { case TeamShareResult::SameTeams: return m_Settings.m_Theme.m_Colors.m_ScoreboardFriendlyTeamBG; case TeamShareResult::OppositeTeams: return m_Settings.m_Theme.m_Colors.m_ScoreboardEnemyTeamBG; + case TeamShareResult::Neither: break; } switch (player.GetTeam()) diff --git a/tf2_bot_detector/UI/MainWindow.cpp b/tf2_bot_detector/UI/MainWindow.cpp index e71b0e9d..426f9007 100644 --- a/tf2_bot_detector/UI/MainWindow.cpp +++ b/tf2_bot_detector/UI/MainWindow.cpp @@ -806,6 +806,8 @@ void MainWindow::OnConsoleLineParsed(IWorldState& world, IConsoleLine& parsed) break; } + + default: break; } } diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index b4660b66..bc43c60c 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -98,6 +98,9 @@ MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(UpToDate) MH_ENUM_REFLECT_VALUE(UpdateAvailable) + MH_ENUM_REFLECT_VALUE(UpdateToolDownloading) + MH_ENUM_REFLECT_VALUE(UpdateToolDownloadingFailed) + MH_ENUM_REFLECT_VALUE(Updating) MH_ENUM_REFLECT_VALUE(UpdateFailed) diff --git a/tf2_bot_detector/Util/TextUtils.cpp b/tf2_bot_detector/Util/TextUtils.cpp index 2fe88b97..2afb4e37 100644 --- a/tf2_bot_detector/Util/TextUtils.cpp +++ b/tf2_bot_detector/Util/TextUtils.cpp @@ -1,9 +1,10 @@ -#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 1 -#define _SILENCE_CXX20_CODECVT_FACETS_DEPRECATION_WARNING 1 +//#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING 1 +//#define _SILENCE_CXX20_CODECVT_FACETS_DEPRECATION_WARNING 1 #include "TextUtils.h" #include "Log.h" +#include #include #include @@ -14,9 +15,7 @@ using namespace std::string_literals; std::u16string tf2_bot_detector::ToU16(const std::u8string_view& input) { - std::wstring_convert, char16_t> converter; - return converter.from_bytes(reinterpret_cast(input.data()), - reinterpret_cast(input.data()) + input.size()); + return mh::change_encoding(input); } std::u16string tf2_bot_detector::ToU16(const char* input, const char* input_end) @@ -44,8 +43,7 @@ std::u8string tf2_bot_detector::ToU8(const std::string_view& input) std::u8string tf2_bot_detector::ToU8(const std::u16string_view& input) { - std::wstring_convert, char16_t> converter; - return ToU8(converter.to_bytes(input.data(), input.data() + input.size())); + return mh::change_encoding(input); } std::u8string tf2_bot_detector::ToU8(const std::wstring_view& input) @@ -70,8 +68,7 @@ std::string tf2_bot_detector::ToMB(const std::wstring_view& input) std::wstring tf2_bot_detector::ToWC(const std::string_view& input) { - std::wstring_convert> converter; - return converter.from_bytes(input.data(), input.data() + input.size()); + return mh::change_encoding(input); } std::u16string tf2_bot_detector::ReadWideFile(const std::filesystem::path& filename) diff --git a/tf2_bot_detector/Version.cpp b/tf2_bot_detector/Version.cpp index 5766d864..803348bc 100644 --- a/tf2_bot_detector/Version.cpp +++ b/tf2_bot_detector/Version.cpp @@ -8,7 +8,7 @@ using namespace tf2_bot_detector; std::optional Version::Parse(const char* str) { Version v{}; - const auto count = std::sscanf(str, "%hi.%hi.%hi.%hi", &v.m_Major, &v.m_Minor, &v.m_Patch, &v.m_Build); + const auto count = sscanf_s(str, "%hi.%hi.%hi.%hi", &v.m_Major, &v.m_Minor, &v.m_Patch, &v.m_Build); if (count < 2) return std::nullopt; diff --git a/tf2_bot_detector/WorldState.cpp b/tf2_bot_detector/WorldState.cpp index 40eac0d2..59895d55 100644 --- a/tf2_bot_detector/WorldState.cpp +++ b/tf2_bot_detector/WorldState.cpp @@ -782,6 +782,8 @@ void WorldState::OnConsoleLineParsed(IWorldState& world, IConsoleLine& parsed) case UserMessageType::VotePass: m_IsVoteInProgress = false; break; + default: + break; } break; @@ -821,6 +823,9 @@ void WorldState::OnConsoleLineParsed(IWorldState& world, IConsoleLine& parsed) break; } #endif + + default: + break; } } From 14051a4ad8714db1292135eae19e61860e32bbe8 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 15:10:12 -0700 Subject: [PATCH 149/161] Fixed a compile error in 32-bit builds. --- tf2_bot_detector/Filesystem.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tf2_bot_detector/Filesystem.cpp b/tf2_bot_detector/Filesystem.cpp index 39202220..96a4518a 100644 --- a/tf2_bot_detector/Filesystem.cpp +++ b/tf2_bot_detector/Filesystem.cpp @@ -130,33 +130,32 @@ std::filesystem::path Filesystem::ResolvePath(const std::filesystem::path& path, return std::move(retVal); } -std::string Filesystem::ReadFile(std::filesystem::path path) const +std::string Filesystem::ReadFile(std::filesystem::path path) const try { path = ResolvePath(path, PathUsage::Read); if (path.empty()) return {}; - std::ifstream file(path, std::ios::binary); - if (!file.good()) - throw std::runtime_error(mh::format("{}: Failed to open file {}", __FUNCTION__, path)); + std::ifstream file; + file.exceptions(std::ios::badbit | std::ios::failbit); + file.open(path, std::ios::binary); file.seekg(0, std::ios::end); - if (!file.good()) - throw std::runtime_error(mh::format("{}: Failed to seek to end of {}", __FUNCTION__, path)); const auto length = file.tellg(); file.seekg(0, std::ios::beg); - if (!file.good()) - throw std::runtime_error(mh::format("{}: Failed to seek to beginning of {}", __FUNCTION__, path)); std::string retVal; - retVal.resize(length); + retVal.resize(size_t(length)); file.read(retVal.data(), length); - if (!file.good()) - throw std::runtime_error(mh::format("{}: Failed to read file {}", __FUNCTION__, path)); return retVal; } +catch (const std::exception& e) +{ + LogException(MH_SOURCE_LOCATION_CURRENT(), e, "Filename: {}", path); + throw; +} void Filesystem::WriteFile(std::filesystem::path path, const void* begin, const void* end) const { From 9ae0f139ae1b09b562075268ce226165bebcc243 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 18:17:44 -0700 Subject: [PATCH 150/161] * Implemented the portable update using the common update path. * Hopefully finished implementing the common update path. --- tf2_bot_detector/Platform/Platform.h | 2 + .../Platform/Windows/Processes.cpp | 16 +- .../SetupFlow/UpdateCheckPage.cpp | 16 +- tf2_bot_detector/UpdateManager.cpp | 375 +++++++++++------- tf2_bot_detector/UpdateManager.h | 16 +- 5 files changed, 268 insertions(+), 157 deletions(-) diff --git a/tf2_bot_detector/Platform/Platform.h b/tf2_bot_detector/Platform/Platform.h index f6291402..ef95d8f9 100644 --- a/tf2_bot_detector/Platform/Platform.h +++ b/tf2_bot_detector/Platform/Platform.h @@ -73,6 +73,8 @@ namespace tf2_bot_detector void Launch(const std::filesystem::path& executable, const std::vector& args = {}, bool elevated = false); + void Launch(const std::filesystem::path& executable, const std::string_view& args = {}, + bool elevated = false); int GetCurrentProcessID(); } diff --git a/tf2_bot_detector/Platform/Windows/Processes.cpp b/tf2_bot_detector/Platform/Windows/Processes.cpp index 43bf6218..a0f34b98 100644 --- a/tf2_bot_detector/Platform/Windows/Processes.cpp +++ b/tf2_bot_detector/Platform/Windows/Processes.cpp @@ -261,18 +261,24 @@ void tf2_bot_detector::Processes::RequireTF2NotRunning() void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable, const std::vector& args, bool elevated) { - std::wstring cmdLine; - - //cmdLine << executable << L' '; + std::string cmdLine; for (const auto& arg : args) - cmdLine << std::quoted(mh::change_encoding(arg)) << L' '; + cmdLine << std::quoted(arg) << ' '; + + return Launch(executable, cmdLine, elevated); +} + +void tf2_bot_detector::Processes::Launch(const std::filesystem::path& executable, + const std::string_view& args, bool elevated) +{ + const auto cmdLineWide = mh::change_encoding(args); const auto result = ShellExecuteW( NULL, elevated ? L"runas" : L"open", executable.c_str(), - cmdLine.c_str(), + cmdLineWide.c_str(), nullptr, SW_SHOWDEFAULT); diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index be7bfc28..824f7805 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -165,17 +165,23 @@ namespace ImGui::TextFmt({ 0, 1, 0, 1 }, "Update succeeded. Restart TF2 Bot Detector to apply."); break; + case UpdateStatus::Downloading: + ImGui::TextFmt("Downloading update..."); + break; + case UpdateStatus::DownloadFailed: + ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update."); + break; + case UpdateStatus::DownloadSuccess: + ImGui::TextFmt("Update download complete."); + break; + + case UpdateStatus::UpdateToolRequired: case UpdateStatus::UpdateToolDownloading: ImGui::TextFmt("Downloading updater..."); break; case UpdateStatus::UpdateToolDownloadingFailed: ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update tool."); break; - - //default: - // LogError(MH_SOURCE_LOCATION_CURRENT(), "Unknown UpdateStatus {}", mh::enum_fmt(updateStatus)); - // ImGui::TextFmt({ 1, 0, 0, 1 }, "Unexpected({})", mh::enum_fmt(updateStatus)); - // break; } if (continueButtonMode == ContinueButtonMode::ContinueWithoutUpdating && diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 79131b55..3900b6c4 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -95,8 +95,7 @@ namespace AvailableUpdate(const HTTPClient& client, UpdateManager& parent, BuildInfo&& buildInfo); bool CanSelfUpdate() const override; - void BeginSelfUpdate() const override; - std::future RunPortableUpdate() const; + bool BeginSelfUpdate() const override; const BuildInfo::BuildVariant* m_Updater = nullptr; const BuildInfo::BuildVariant* m_Portable = nullptr; @@ -139,6 +138,12 @@ namespace std::string m_Arguments; }; + struct DownloadedBuild + { + BuildInfo::BuildVariant m_UpdaterVariant; + std::filesystem::path m_ExtractedLocation; + }; + struct UpdateToolResult { bool m_Success{}; @@ -154,6 +159,10 @@ namespace using State_t = std::variant< std::monostate, + std::future, + DownloadedBuild, + ExceptionData, + std::future, InstallUpdate::Result, ExceptionData, @@ -165,14 +174,23 @@ namespace std::future, UpdateToolResult, ExceptionData - >; State_t m_State; template bool UpdateFutureState(); + template bool UpdateFutureStates(); bool CanReplaceUpdateCheckState() const; + inline static const std::filesystem::path DOWNLOAD_DIR_ROOT = + std::filesystem::temp_directory_path() / "TF2 Bot Detector" / "Portable Updates"; + + static std::future DownloadBuild(const HTTPClient& client, + BuildInfo::BuildVariant tool, BuildInfo::BuildVariant updater); + static std::future DownloadUpdateTool(const HTTPClient& client, + BuildInfo::BuildVariant updater, std::string args); + static std::future RunUpdateTool(std::filesystem::path path, std::string args); + bool m_IsUpdateQueued = true; bool m_IsInstalled; }; @@ -205,6 +223,12 @@ namespace return false; } + template + bool UpdateManager::UpdateFutureStates() + { + return (... || UpdateFutureState()); + } + void UpdateManager::Update() { if (m_IsUpdateQueued && CanReplaceUpdateCheckState()) @@ -255,9 +279,59 @@ namespace } } - UpdateFutureState() || - UpdateFutureState() || - UpdateFutureState(); + if (auto downloadedBuild = std::get_if(&m_State)) + { + [&] + { + auto client = m_Settings.GetHTTPClient(); + if (!client) + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable"); + return; + } + + m_State = DownloadUpdateTool(*client, downloadedBuild->m_UpdaterVariant, + mh::format("--update-type Portable --source-path {} --dest-path {}", + downloadedBuild->m_ExtractedLocation, Platform::GetCurrentExeDir())); + }(); + } + else if (auto installUpdateResult = std::get_if(&m_State)) + { + [&] + { + auto needsUpdateTool = std::get_if(installUpdateResult); + if (!needsUpdateTool) + return; + + auto availableUpdate = GetAvailableUpdate(); + if (!availableUpdate) + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "availableUpdate was nullptr"); + return; + } + + if (!availableUpdate->m_Updater) + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "availableUpdate->m_Updater was nullptr"); + return; + } + + auto client = m_Settings.GetHTTPClient(); + if (!client) + { + LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable"); + return; + } + + m_State = DownloadUpdateTool(*client, *availableUpdate->m_Updater, needsUpdateTool->m_UpdateToolArgs); + }(); + } + else if (auto downloadedUpdateTool = std::get_if(&m_State)) + { + m_State = RunUpdateTool(downloadedUpdateTool->m_Path, downloadedUpdateTool->m_Arguments); + } + + UpdateFutureStates(); } UpdateStatus UpdateManager::GetUpdateStatus() const @@ -277,6 +351,16 @@ namespace case mh::variant_type_index_v: break; + /////////////////////// + /// DownloadedBuild /// + /////////////////////// + case mh::variant_type_index_v>: + return UpdateStatus::Downloading; + case mh::variant_type_index_v>: + return UpdateStatus::DownloadFailed; + case mh::variant_type_index_v: + return UpdateStatus::DownloadSuccess; + ////////////////////////////// /// InstallUpdate::Result /// ////////////////////////////// @@ -298,7 +382,7 @@ namespace case mh::variant_type_index_v: return UpdateStatus::Updating; case mh::variant_type_index_v: - return UpdateStatus::UpdateToolDownloading; + return UpdateStatus::UpdateToolRequired; } } @@ -388,6 +472,140 @@ namespace return false; } + static void SaveFile(const std::filesystem::path& path, const void* dataBegin, const void* dataEnd) + { + std::filesystem::create_directories(mh::copy(path).remove_filename()); + + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file.exceptions(std::ios::badbit | std::ios::failbit); + file.write(reinterpret_cast(dataBegin), + static_cast(dataEnd) - static_cast(dataBegin)); + } + + static void ExtractArchive(const libzippp::ZipArchive& archive, const std::filesystem::path& directory) + { + try + { + std::filesystem::create_directories(directory); + } + catch (...) + { + std::throw_with_nested(std::runtime_error(mh::format("{}: Failed to create directory(s) for {}", + MH_SOURCE_LOCATION_CURRENT(), directory))); + } + + try + { + for (const auto& entry : archive.getEntries()) + { + const std::filesystem::path path = directory / entry.getName(); + if (!entry.isFile()) + continue; + + std::filesystem::create_directories(mh::copy(path).remove_filename()); + + std::ofstream file(path, std::ios::binary); + file.exceptions(std::ios::badbit | std::ios::failbit); + + const auto result = entry.readContent(file); + if (result != LIBZIPPP_OK) + { + throw std::runtime_error(mh::format("{}: entry.readContent() returned {}", + MH_SOURCE_LOCATION_CURRENT(), result)); + } + } + } + catch (...) + { + std::throw_with_nested(std::runtime_error(mh::format("{}: Failed to extract archive entries", + MH_SOURCE_LOCATION_CURRENT()))); + } + } + + static void DownloadAndExtractZip(const HTTPClient& client, const URL& url, + const std::filesystem::path& extractDir) + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Downloading {}...", url); + const auto data = client.GetString(url); + + // Need to save to a file due to a libzippp bug in ZipArchive::fromBuffer + const auto tempZipPath = mh::copy(extractDir).replace_extension(".zip"); + Log(MH_SOURCE_LOCATION_CURRENT(), "Saving zip to {}...", tempZipPath); + + mh::scope_exit scopeExit([&] + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Deleting {}...", tempZipPath); + std::filesystem::remove(tempZipPath); + }); + + SaveFile(tempZipPath, data.data(), data.data() + data.size()); + + { + using namespace libzippp; + Log(MH_SOURCE_LOCATION_CURRENT(), "Extracting {} to {}...", tempZipPath, extractDir); + ZipArchive archive(tempZipPath.string()); + archive.open(); + ExtractArchive(archive, extractDir); + } + } + + auto UpdateManager::DownloadBuild(const HTTPClient& client, + BuildInfo::BuildVariant tool, BuildInfo::BuildVariant updater) -> + std::future + { + const auto downloadDir = DOWNLOAD_DIR_ROOT / mh::format("tool_{}", + std::chrono::high_resolution_clock::now().time_since_epoch().count()); + + auto clientPtr = client.shared_from_this(); + return std::async([clientPtr, tool, updater, downloadDir]() -> DownloadedBuild + { + DownloadAndExtractZip(*clientPtr, tool.m_DownloadURL, downloadDir); + + return DownloadedBuild + { + .m_UpdaterVariant = updater, + .m_ExtractedLocation = downloadDir, + }; + }); + } + + auto UpdateManager::DownloadUpdateTool(const HTTPClient& client, BuildInfo::BuildVariant updater, + std::string args) -> std::future + { + auto clientPtr = client.shared_from_this(); + + return std::async([clientPtr, updater, args]() -> DownloadedUpdateTool + { + const auto downloadDir = DOWNLOAD_DIR_ROOT / mh::format("updater_{}", + std::chrono::high_resolution_clock::now().time_since_epoch().count()); + + DownloadAndExtractZip(*clientPtr, updater.m_DownloadURL, downloadDir); + + return DownloadedUpdateTool + { + // FIXME linux + .m_Path = downloadDir / "tf2_bot_detector_updater.exe", + .m_Arguments = args, + }; + }); + } + + auto UpdateManager::RunUpdateTool(std::filesystem::path path, std::string args) -> + std::future + { + // For now, all of our paths through the update tool require that we close ourselves + args += mh::format(" --wait-pid {}", Platform::Processes::GetCurrentProcessID()); + + return std::async([path, args]() -> UpdateToolResult + { + Log(MH_SOURCE_LOCATION_CURRENT(), "Launching updater...\n\tArgs: {}", args); + Platform::Processes::Launch(path, args); + + LogWarning(MH_SOURCE_LOCATION_CURRENT(), "Exiting now for portable-mode update..."); + std::exit(1); + }); + } + static const BuildInfo::BuildVariant* FindNativeVariant(const std::vector& variants) { const auto os = Platform::GetOS(); @@ -446,19 +664,19 @@ namespace return false; } - void UpdateManager::AvailableUpdate::BeginSelfUpdate() const + bool UpdateManager::AvailableUpdate::BeginSelfUpdate() const { if (!CanSelfUpdate()) { LogError(MH_SOURCE_LOCATION_CURRENT(), "{} called when CanSelfUpdate() returned false", __func__); - return; + return false; } auto client = m_Parent.m_Settings.GetHTTPClient(); if (!client) { LogError(MH_SOURCE_LOCATION_CURRENT(), "client was nullptr"); - std::terminate(); + return false; } if (Platform::IsInstalled()) @@ -467,151 +685,22 @@ namespace { Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed and able to install update"); m_Parent.m_State = Platform::BeginInstallUpdate(m_BuildInfo, *client); + return true; } else { LogError(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed but cannot install update"); + return false; } } else { Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports *not* being installed. Running portable update."); - m_Parent.m_State = RunPortableUpdate(); - } - } - - static void ExtractArchive(const libzippp::ZipArchive& archive, const std::filesystem::path& directory) - { - try - { - std::filesystem::create_directories(directory); - } - catch (...) - { - std::throw_with_nested(std::runtime_error(mh::format("{}: Failed to create directory(s) for {}", - MH_SOURCE_LOCATION_CURRENT(), directory))); - } - - try - { - for (const auto& entry : archive.getEntries()) - { - const std::filesystem::path path = directory / entry.getName(); - if (!entry.isFile()) - continue; - - std::filesystem::create_directories(mh::copy(path).remove_filename()); - - std::ofstream file(path, std::ios::binary); - file.exceptions(std::ios::badbit | std::ios::failbit); - - const auto result = entry.readContent(file); - if (result != LIBZIPPP_OK) - { - throw std::runtime_error(mh::format("{}: entry.readContent() returned {}", - MH_SOURCE_LOCATION_CURRENT(), result)); - } - } - } - catch (...) - { - std::throw_with_nested(std::runtime_error(mh::format("{}: Failed to extract archive entries", - MH_SOURCE_LOCATION_CURRENT()))); - } - } - - static void SaveFile(const std::filesystem::path& path, const void* dataBegin, const void* dataEnd) - { - std::filesystem::create_directories(mh::copy(path).remove_filename()); - - std::ofstream file(path, std::ios::binary | std::ios::trunc); - file.exceptions(std::ios::badbit | std::ios::failbit); - file.write(reinterpret_cast(dataBegin), - static_cast(dataEnd) - static_cast(dataBegin)); - } - - static void DownloadAndExtractZip(const HTTPClient& client, const URL& url, - const std::filesystem::path& extractDir) - { - Log(MH_SOURCE_LOCATION_CURRENT(), "Downloading {}...", url); - const auto data = client.GetString(url); - - // Need to save to a file due to a libzippp bug in ZipArchive::fromBuffer - const auto tempZipPath = mh::copy(extractDir).replace_extension(".zip"); - Log(MH_SOURCE_LOCATION_CURRENT(), "Saving zip to {}...", tempZipPath); - - mh::scope_exit scopeExit([&] - { - Log(MH_SOURCE_LOCATION_CURRENT(), "Deleting {}...", tempZipPath); - std::filesystem::remove(tempZipPath); - }); - - SaveFile(tempZipPath, data.data(), data.data() + data.size()); - - { - using namespace libzippp; - Log(MH_SOURCE_LOCATION_CURRENT(), "Extracting {} to {}...", tempZipPath, extractDir); - ZipArchive archive(tempZipPath.string()); - archive.open(); - ExtractArchive(archive, extractDir); + m_Parent.m_State = DownloadBuild(*client, *m_Portable, *m_Updater); + return true; } } - std::future - UpdateManager::AvailableUpdate::RunPortableUpdate() const - { - if (!m_Updater) - throw std::logic_error("Updater was null, we should never get here"); - if (!m_Portable) - throw std::logic_error("Portable was null, we should never get here"); - - auto updaterVariant = *m_Updater; - auto toolVariant = *m_Portable; - auto client = m_HTTPClient; - return std::async([updaterVariant, toolVariant, client]() -> UpdateManager::UpdateToolResult - { - const auto tempDirRoot = - std::filesystem::temp_directory_path() / "TF2 Bot Detector" / "Portable Updates"; - const auto tempDirUpdater = tempDirRoot / mh::format("updater_{}", - std::chrono::high_resolution_clock::now().time_since_epoch().count()); - const auto tempDirTool = tempDirRoot / mh::format("tool_{}", - std::chrono::high_resolution_clock::now().time_since_epoch().count()); - - DownloadAndExtractZip(*client, updaterVariant.m_DownloadURL, tempDirUpdater); - DownloadAndExtractZip(*client, toolVariant.m_DownloadURL, tempDirTool); - - // FIXME linux - const auto updaterPath = tempDirUpdater / "tf2_bot_detector_updater.exe"; - const auto newVersionPath = tempDirTool; - const auto extractionPath = Platform::GetCurrentExeDir(); - const auto pid = Platform::Processes::GetCurrentProcessID(); - - // Run updater - Log(MH_SOURCE_LOCATION_CURRENT(), - "Launching updater..." - "\n\tUpdater Path: {}" - "\n\tNew version path: {}" - "\n\tExtraction path: {}" - "\n\tPID: {}", - updaterPath, - newVersionPath, - extractionPath, - pid - ); - - Platform::Processes::Launch(updaterPath, - { - "--update-type", "Portable", - "--source-path", newVersionPath.string(), - "--dest-path", extractionPath.string(), - "--wait-pid", std::to_string(pid), - }); - - LogWarning(MH_SOURCE_LOCATION_CURRENT(), "Exiting now for portable-mode update..."); - std::exit(1); - }); - } - UpdateManager::BaseExceptionData::BaseExceptionData(const std::type_info& type, std::string message, const std::exception_ptr& exception) : m_Type(type), m_Message(std::move(message)), m_Exception(exception) diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index bc43c60c..245338c3 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -40,8 +40,8 @@ namespace tf2_bot_detector IAvailableUpdate(BuildInfo&& bi) : m_BuildInfo(std::move(bi)) {} virtual ~IAvailableUpdate() = default; - virtual bool CanSelfUpdate() const = 0; - virtual void BeginSelfUpdate() const = 0; + [[nodiscard]] virtual bool CanSelfUpdate() const = 0; + virtual bool BeginSelfUpdate() const = 0; BuildInfo m_BuildInfo; }; @@ -60,11 +60,15 @@ namespace tf2_bot_detector UpToDate, UpdateAvailable, + UpdateToolRequired, UpdateToolDownloading, UpdateToolDownloadingFailed, - Updating, + Downloading, + DownloadFailed, + DownloadSuccess, + Updating, UpdateFailed, UpdateSuccess, }; @@ -98,11 +102,15 @@ MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(UpToDate) MH_ENUM_REFLECT_VALUE(UpdateAvailable) + MH_ENUM_REFLECT_VALUE(UpdateToolRequired) MH_ENUM_REFLECT_VALUE(UpdateToolDownloading) MH_ENUM_REFLECT_VALUE(UpdateToolDownloadingFailed) - MH_ENUM_REFLECT_VALUE(Updating) + MH_ENUM_REFLECT_VALUE(Downloading) + MH_ENUM_REFLECT_VALUE(DownloadFailed) + MH_ENUM_REFLECT_VALUE(DownloadSuccess) + MH_ENUM_REFLECT_VALUE(Updating) MH_ENUM_REFLECT_VALUE(UpdateFailed) MH_ENUM_REFLECT_VALUE(UpdateSuccess) MH_ENUM_REFLECT_END() From 360ddf8a8a0a554ea31c7bea60b3b0f912256167 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 21:33:55 -0700 Subject: [PATCH 151/161] Much more robust update state tracking and reporting --- .../SetupFlow/UpdateCheckPage.cpp | 169 ++++++++---- tf2_bot_detector/UpdateManager.cpp | 258 ++++++++++++------ tf2_bot_detector/UpdateManager.h | 14 +- 3 files changed, 292 insertions(+), 149 deletions(-) diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index 824f7805..f5c48967 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -7,6 +7,7 @@ #include "UpdateManager.h" #include +#include using namespace tf2_bot_detector; @@ -26,7 +27,7 @@ namespace bool WantsContinueButton() const override { return false; } private: - UpdateStatus m_LastUpdateStatus = UpdateStatus::Unknown; + mh::status_reader m_StatusReader; bool m_HasChangedReleaseChannel = false; bool m_HasCheckedForUpdate = false; bool m_UpdateButtonPressed = false; @@ -48,7 +49,11 @@ namespace { m_HasCheckedForUpdate = true; - const UpdateStatus updateStatus = m_LastUpdateStatus = ds.m_UpdateManager->GetUpdateStatus(); + if (!m_StatusReader.has_value()) + m_StatusReader = ds.m_UpdateManager->GetUpdateStatus(); + + const auto updateStatus = m_StatusReader.get(); + const IAvailableUpdate* update = ds.m_UpdateManager->GetAvailableUpdate(); ImGui::TextFmt("Update Check"); @@ -74,53 +79,69 @@ namespace } continueButtonMode = ContinueButtonMode::None; - switch (updateStatus) + // Early out + switch (updateStatus.m_Status) { - case UpdateStatus::Unknown: - ImGui::TextFmt({ 1, 1, 0, 1 }, "Unknown"); - continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; - break; - case UpdateStatus::UpdateCheckDisabled: { - ImGui::TextFmt("Automatic update checks disabled by user"); if (!m_HasChangedReleaseChannel) return OnDrawResult::EndDrawing; - ImGui::NewLine(); - continueButtonMode = ContinueButtonMode::Continue; break; } + case UpdateStatus::InternetAccessDisabled: - ImGui::TextFmt("Internet connectivity disabled by user"); return OnDrawResult::EndDrawing; - case UpdateStatus::CheckQueued: - ImGui::TextFmt("Update check queued..."); - break; - case UpdateStatus::Checking: - ImGui::TextFmt("Checking for updates..."); + default: break; + } + // Message color + ImVec4 msgColor = { 1, 0, 1, 1 }; + switch (updateStatus.m_Status) + { + // Red + case UpdateStatus::StateSwitchFailure: case UpdateStatus::CheckFailed: - ImGui::TextFmt({ 1, 1, 0, 1 }, "Update check failed"); - continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; - ImGui::NewLine(); + case UpdateStatus::UpdateToolDownloadFailed: + case UpdateStatus::DownloadFailed: + case UpdateStatus::UpdateFailed: + msgColor = { 1, 0, 0, 1 }; break; + + // Green case UpdateStatus::UpToDate: - { - ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, - mh::enum_fmt(ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public))); + case UpdateStatus::UpdateSuccess: + msgColor = { 0, 1, 0, 1 }; + break; - if (!m_HasChangedReleaseChannel) - return OnDrawResult::EndDrawing; + // Cyan + case UpdateStatus::UpdateAvailable: + msgColor = { 0, 1, 1, 1 }; + break; - ImGui::NewLine(); - continueButtonMode = ContinueButtonMode::Continue; + // Yellow + case UpdateStatus::Unknown: + case UpdateStatus::UpdateCheckDisabled: + case UpdateStatus::InternetAccessDisabled: + msgColor = { 1, 1, 0, 1 }; + break; + // White + case UpdateStatus::CheckQueued: + case UpdateStatus::Checking: + case UpdateStatus::UpdateToolRequired: + case UpdateStatus::UpdateToolDownloading: + case UpdateStatus::UpdateToolDownloadSuccess: + case UpdateStatus::Downloading: + case UpdateStatus::DownloadSuccess: + case UpdateStatus::Updating: + msgColor = { 1, 1, 1, 1 }; break; } - case UpdateStatus::UpdateAvailable: + + if (updateStatus.m_Status == UpdateStatus::UpdateAvailable) { ImGui::TextFmt({ 0, 1, 1, 1 }, "Update available: v{} {:v} (current version v{})", update->m_BuildInfo.m_Version, mh::enum_fmt(update->m_BuildInfo.m_ReleaseChannel), VERSION); @@ -150,51 +171,75 @@ namespace }, "Unable to determine GitHub URL"); ImGui::SameLine(); - continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; - break; + } + else if (updateStatus.m_Status == UpdateStatus::Unknown) + { + ImGui::TextFmt(msgColor, "UNKNOWN UPDATE STATUS: {}", std::quoted(updateStatus.m_Message)); + } + else + { + assert(!updateStatus.m_Message.empty()); + ImGui::TextFmt(msgColor, updateStatus.m_Message); } - case UpdateStatus::Updating: - ImGui::TextFmt("Updating..."); - break; +#if 0 + switch (updateStatus.m_Status) + { + case UpdateStatus::UpToDate: + { + ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, + mh::enum_fmt(ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public))); + + if (!m_HasChangedReleaseChannel) + return OnDrawResult::EndDrawing; + + ImGui::NewLine(); + continueButtonMode = ContinueButtonMode::Continue; - case UpdateStatus::UpdateFailed: - ImGui::TextFmt({ 1, 0, 0, 1 }, "Update failed"); - break; - case UpdateStatus::UpdateSuccess: - ImGui::TextFmt({ 0, 1, 0, 1 }, "Update succeeded. Restart TF2 Bot Detector to apply."); break; + } + } +#endif + + // Continue button mode + switch (updateStatus.m_Status) + { + case UpdateStatus::Unknown: + case UpdateStatus::CheckFailed: + case UpdateStatus::StateSwitchFailure: + case UpdateStatus::UpdateAvailable: + { + if (ImGui::Button("Continue without updating >")) + return OnDrawResult::EndDrawing; - case UpdateStatus::Downloading: - ImGui::TextFmt("Downloading update..."); break; + } + + case UpdateStatus::UpToDate: + case UpdateStatus::UpdateCheckDisabled: + case UpdateStatus::InternetAccessDisabled: + case UpdateStatus::UpdateSuccess: + case UpdateStatus::UpdateToolDownloadFailed: case UpdateStatus::DownloadFailed: - ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update."); - break; - case UpdateStatus::DownloadSuccess: - ImGui::TextFmt("Update download complete."); + case UpdateStatus::UpdateFailed: + { + if (ImGui::Button("Continue >")) + return OnDrawResult::EndDrawing; + break; + } + case UpdateStatus::CheckQueued: + case UpdateStatus::Checking: case UpdateStatus::UpdateToolRequired: case UpdateStatus::UpdateToolDownloading: - ImGui::TextFmt("Downloading updater..."); - break; - case UpdateStatus::UpdateToolDownloadingFailed: - ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update tool."); + case UpdateStatus::UpdateToolDownloadSuccess: + case UpdateStatus::Downloading: + case UpdateStatus::DownloadSuccess: + case UpdateStatus::Updating: break; } - if (continueButtonMode == ContinueButtonMode::ContinueWithoutUpdating && - ImGui::Button("Continue without updating >")) - { - return OnDrawResult::EndDrawing; - } - if (continueButtonMode == ContinueButtonMode::Continue && - ImGui::Button("Continue >")) - { - return OnDrawResult::EndDrawing; - } - return OnDrawResult::ContinueDrawing; } @@ -209,9 +254,13 @@ namespace if (!m_HasCheckedForUpdate) return false; - return mh::none_eq(m_LastUpdateStatus + return mh::none_eq(m_StatusReader.get().m_Status , UpdateStatus::CheckQueued , UpdateStatus::Checking + , UpdateStatus::UpdateToolRequired + , UpdateStatus::UpdateToolDownloading + , UpdateStatus::Downloading + , UpdateStatus::DownloadSuccess , UpdateStatus::Updating ); } diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 3900b6c4..d592e850 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -108,8 +108,7 @@ namespace UpdateManager(const Settings& settings); void Update() override; - UpdateStatus GetUpdateStatus() const override; - std::string GetErrorString() const override; + mh::status_reader GetUpdateStatus() const override { return m_State.GetUpdateStatus(); } const AvailableUpdate* GetAvailableUpdate() const override; void QueueUpdateCheck() override { m_IsUpdateQueued = true; } @@ -148,37 +147,84 @@ namespace { bool m_Success{}; }; - struct UpdateToolExceptionData : BaseExceptionData - { - using BaseExceptionData::BaseExceptionData; - }; - - using UpdateCheckState_t = std::variant, AvailableUpdate, ExceptionData>; - UpdateCheckState_t m_UpdateCheckState; using State_t = std::variant< std::monostate, std::future, DownloadedBuild, - ExceptionData, std::future, InstallUpdate::Result, - ExceptionData, std::future, DownloadedUpdateTool, - ExceptionData, std::future, - UpdateToolResult, - ExceptionData + UpdateToolResult >; - State_t m_State; + using UpdateCheckState_t = std::variant, AvailableUpdate>; + + struct StateManager + { + const State_t& GetVariant() const { return m_Variant; } + UpdateCheckState_t& GetUpdateCheckVariant() { return m_UpdateCheckVariant; } + const UpdateCheckState_t& GetUpdateCheckVariant() const { return m_UpdateCheckVariant; } + + template + void Emplace(const mh::source_location& location, UpdateStatus status, std::string msg, TArgs&&... args) + { + SetUpdateStatus(location, status, std::move(msg)); + m_Variant.emplace(std::forward(args)...); + } + + void Clear(const mh::source_location& location, UpdateStatus status, std::string msg) + { + Emplace(location, status, std::move(msg)); + } + void ClearUpdateCheck(const mh::source_location& location, UpdateStatus status, std::string msg) + { + SetUpdateStatus(location, status, std::move(msg)); + m_UpdateCheckVariant.emplace<0>(); + } + + template + void Set(const mh::source_location& location, UpdateStatus status, std::string msg, T&& value) + { + SetUpdateStatus(location, status, std::move(msg)); + m_Variant = std::forward(value); + } + + template + void SetUpdateCheck(const mh::source_location& location, UpdateStatus status, std::string msg, T&& value) + { + SetUpdateStatus(location, status, std::move(msg)); + m_UpdateCheckVariant.emplace(std::forward(value)); + } + + void SetUpdateStatus(const mh::source_location& location, UpdateStatus status, + const std::string_view& msg); + mh::status_reader GetUpdateStatus() const { return m_UpdateStatus; } - template bool UpdateFutureState(); - template bool UpdateFutureStates(); + template bool Update(const mh::source_location& location, + UpdateStatus success, const std::string_view& successMsg, + UpdateStatus failure, const std::string_view& failureMsg) + { + return UpdateVariant(m_Variant, location, + success, successMsg, failure, failureMsg); + } + + private: + template + bool UpdateVariant(TVariant& variant, const mh::source_location& location, + UpdateStatus success, const std::string_view& successMsg, + UpdateStatus failure, const std::string_view& failureMsg); + + UpdateCheckState_t m_UpdateCheckVariant; + State_t m_Variant; + mh::status_source m_UpdateStatus; + + } m_State; bool CanReplaceUpdateCheckState() const; @@ -201,20 +247,27 @@ namespace { } - template - bool UpdateManager::UpdateFutureState() + template + bool UpdateManager::StateManager::UpdateVariant(TVariant& variant, const mh::source_location& location, + UpdateStatus success, const std::string_view& successMsg, + UpdateStatus failure, const std::string_view& failureMsg) { - if (auto future = std::get_if>(&m_State)) + if (auto future = std::get_if>(&variant)) { try { if (mh::is_future_ready(*future)) - m_State.emplace(future->get()); + { + auto value = future->get(); + SetUpdateStatus(MH_SOURCE_LOCATION_CURRENT(), success, std::string(successMsg)); + variant.emplace(std::move(value)); + } } catch (const std::exception& e) { LogException(MH_SOURCE_LOCATION_CURRENT(), e, __FUNCSIG__); - m_State.emplace>(typeid(e), e.what(), std::current_exception()); + Clear(MH_SOURCE_LOCATION_CURRENT(), failure, + mh::format("{}:\n\t- {}\n\t- {}", failureMsg, typeid(e).name(), e.what())); } return true; @@ -223,12 +276,6 @@ namespace return false; } - template - bool UpdateManager::UpdateFutureStates() - { - return (... || UpdateFutureState()); - } - void UpdateManager::Update() { if (m_IsUpdateQueued && CanReplaceUpdateCheckState()) @@ -238,25 +285,27 @@ namespace if (client && (releaseChannel != ReleaseChannel::None)) { auto sharedClient = client->shared_from_this(); - m_UpdateCheckState = std::async([sharedClient, releaseChannel]() -> BuildInfo - { - const mh::fmtstr<256> url( - "https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.json?type={:v}", - mh::enum_fmt(releaseChannel)); - DebugLog("HTTP GET {}", url); - auto response = sharedClient->GetString(url.view()); + m_State.SetUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Checking, "Checking for updates...", + std::async([sharedClient, releaseChannel]() -> BuildInfo + { + const mh::fmtstr<256> url( + "https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.json?type={:v}", + mh::enum_fmt(releaseChannel)); - auto json = nlohmann::json::parse(response); + DebugLog("HTTP GET {}", url); + auto response = sharedClient->GetString(url.view()); - return json.get(); - }); + auto json = nlohmann::json::parse(response); + + return json.get(); + })); m_IsUpdateQueued = false; } } - if (auto future = std::get_if>(&m_UpdateCheckState)) + if (auto future = std::get_if>(&m_State.GetUpdateCheckVariant())) { try { @@ -264,38 +313,59 @@ namespace if (client) { if (mh::is_future_ready(*future)) - m_UpdateCheckState.emplace(*client, *this, future->get()); + { + AvailableUpdate update(*client, *this, future->get()); + + if (update.m_BuildInfo.m_Version <= VERSION) + { + m_State.SetUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpToDate, + mh::format("Up to date (v{} {:v})", VERSION, mh::enum_fmt( + m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::Public))), + std::move(update)); + } + else + { + m_State.SetUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateAvailable, + mh::format("Update available (v{} {:v})", update.m_BuildInfo.m_Version, mh::enum_fmt( + m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::Public))), + std::move(update)); + } + } } else { - LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable, cancelling update"); - m_UpdateCheckState.emplace<0>(); + m_State.ClearUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::CheckFailed, + "Update check failed: HTTPClient unavailable"); } } catch (const std::exception& e) { - LogException(MH_SOURCE_LOCATION_CURRENT(), e, __FUNCSIG__); - m_UpdateCheckState.emplace>(typeid(e), e.what(), std::current_exception()); + m_State.ClearUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::CheckFailed, + mh::format("Update check failed:\n\t- {}\n\t- {}", typeid(e).name(), e.what())); } } - if (auto downloadedBuild = std::get_if(&m_State)) + if (auto downloadedBuild = std::get_if(&m_State.GetVariant())) { [&] { auto client = m_Settings.GetHTTPClient(); if (!client) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: HTTPClient unavailable"); return; } - m_State = DownloadUpdateTool(*client, downloadedBuild->m_UpdaterVariant, - mh::format("--update-type Portable --source-path {} --dest-path {}", - downloadedBuild->m_ExtractedLocation, Platform::GetCurrentExeDir())); + m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateToolDownloading, + "New version downloaded. Downloading update tool...", + + DownloadUpdateTool(*client, downloadedBuild->m_UpdaterVariant, + mh::format("--update-type Portable --source-path {} --dest-path {}", + downloadedBuild->m_ExtractedLocation, Platform::GetCurrentExeDir()))); }(); } - else if (auto installUpdateResult = std::get_if(&m_State)) + else if (auto installUpdateResult = std::get_if(&m_State.GetVariant())) { [&] { @@ -306,34 +376,58 @@ namespace auto availableUpdate = GetAvailableUpdate(); if (!availableUpdate) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "availableUpdate was nullptr"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: availableUpdate was nullptr"); return; } if (!availableUpdate->m_Updater) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "availableUpdate->m_Updater was nullptr"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: availableUpdate->m_Updater was nullptr"); return; } auto client = m_Settings.GetHTTPClient(); if (!client) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: HTTPClient unavailable"); return; } - m_State = DownloadUpdateTool(*client, *availableUpdate->m_Updater, needsUpdateTool->m_UpdateToolArgs); + m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateToolDownloading, + "Platform app updater unavailable. Downloading update tool...", + + DownloadUpdateTool(*client, *availableUpdate->m_Updater, needsUpdateTool->m_UpdateToolArgs)); }(); } - else if (auto downloadedUpdateTool = std::get_if(&m_State)) + else if (auto downloadedUpdateTool = std::get_if(&m_State.GetVariant())) { - m_State = RunUpdateTool(downloadedUpdateTool->m_Path, downloadedUpdateTool->m_Arguments); + m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Updating, + "Running update tool...", + + RunUpdateTool(downloadedUpdateTool->m_Path, downloadedUpdateTool->m_Arguments)); } - UpdateFutureStates(); + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::DownloadSuccess, "Finished downloading new version.", + UpdateStatus::DownloadFailed, "Failed to download new version."); + + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::UpdateSuccess, "Finished running platform update.", + UpdateStatus::UpdateFailed, "Failed to run platform update."); + + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::UpdateToolDownloadSuccess, "Finished downloading update tool.", + UpdateStatus::UpdateToolDownloadFailed, "Failed to download update tool."); + + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::UpdateSuccess, "Update complete.", + UpdateStatus::UpdateFailed, "Update failed."); } +#if 0 UpdateStatus UpdateManager::GetUpdateStatus() const { if (m_IsUpdateQueued) @@ -427,31 +521,11 @@ namespace m_UpdateCheckState.index(), m_State.index()); return UpdateStatus::Unknown; } - - std::string UpdateManager::GetErrorString() const - { - return std::visit([](const auto& value) -> std::string - { - using type_t = std::decay_t; - if constexpr (std::is_base_of_v) - { - const BaseExceptionData& d = value; - return mh::format("{}: {}"sv, d.m_Type.name(), d.m_Message); - } - else - { - return {}; - } - - }, m_State); - } +#endif auto UpdateManager::GetAvailableUpdate() const -> const AvailableUpdate* { - if (GetUpdateStatus() == UpdateStatus::UpdateAvailable) - return std::get_if(&m_UpdateCheckState); - - return nullptr; + return std::get_if(&m_State.GetUpdateCheckVariant()); } /// @@ -459,7 +533,7 @@ namespace /// bool UpdateManager::CanReplaceUpdateCheckState() const { - const auto* future = std::get_if>(&m_UpdateCheckState); + const auto* future = std::get_if>(&m_State.GetUpdateCheckVariant()); if (!future) return true; // some other value in the variant @@ -683,20 +757,27 @@ namespace { if (Platform::CanInstallUpdate(m_BuildInfo)) { - Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed and able to install update"); - m_Parent.m_State = Platform::BeginInstallUpdate(m_BuildInfo, *client); + m_Parent.m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Updating, + "Platform reports that TF2 Bot Detector is already installed, and it can be updated. Running platform updater...", + + Platform::BeginInstallUpdate(m_BuildInfo, *client)); + return true; } else { - LogError(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed but cannot install update"); + m_Parent.m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Platform reports that TF2 Bot Detector is installed, but it is unable to install updates."); return false; } } else { - Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports *not* being installed. Running portable update."); - m_Parent.m_State = DownloadBuild(*client, *m_Portable, *m_Updater); + m_Parent.m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Downloading, + "Platform reports that TF2 Bot Detector is not installed. Updating in-place (portable mode)", + + DownloadBuild(*client, *m_Portable, *m_Updater)); + return true; } } @@ -706,6 +787,13 @@ namespace m_Type(type), m_Message(std::move(message)), m_Exception(exception) { } + + void UpdateManager::StateManager::SetUpdateStatus( + const mh::source_location& location, UpdateStatus status, const std::string_view& msg) + { + if (m_UpdateStatus.set(status, msg)) + DebugLog(location, "{}: {}", mh::enum_fmt(status), msg); + } } std::unique_ptr tf2_bot_detector::IUpdateManager::Create(const Settings& settings) diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index 245338c3..87d2e866 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -4,6 +4,7 @@ #include "ReleaseChannel.h" #include "Version.h" +#include #include #include @@ -50,6 +51,8 @@ namespace tf2_bot_detector { Unknown = 0, + StateSwitchFailure, + UpdateCheckDisabled, InternetAccessDisabled, @@ -62,7 +65,8 @@ namespace tf2_bot_detector UpdateToolRequired, UpdateToolDownloading, - UpdateToolDownloadingFailed, + UpdateToolDownloadFailed, + UpdateToolDownloadSuccess, Downloading, DownloadFailed, @@ -84,8 +88,7 @@ namespace tf2_bot_detector virtual void Update() = 0; - virtual UpdateStatus GetUpdateStatus() const = 0; - virtual std::string GetErrorString() const = 0; + virtual mh::status_reader GetUpdateStatus() const = 0; virtual const IAvailableUpdate* GetAvailableUpdate() const = 0; }; } @@ -93,6 +96,8 @@ namespace tf2_bot_detector MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(Unknown) + MH_ENUM_REFLECT_VALUE(StateSwitchFailure) + MH_ENUM_REFLECT_VALUE(UpdateCheckDisabled) MH_ENUM_REFLECT_VALUE(InternetAccessDisabled) @@ -104,7 +109,8 @@ MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(UpdateToolRequired) MH_ENUM_REFLECT_VALUE(UpdateToolDownloading) - MH_ENUM_REFLECT_VALUE(UpdateToolDownloadingFailed) + MH_ENUM_REFLECT_VALUE(UpdateToolDownloadFailed) + MH_ENUM_REFLECT_VALUE(UpdateToolDownloadSuccess) MH_ENUM_REFLECT_VALUE(Downloading) MH_ENUM_REFLECT_VALUE(DownloadFailed) From 6ae7078027d7cff39b0cf6b2204d3528a22f1e89 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 21:33:55 -0700 Subject: [PATCH 152/161] Much more robust update state tracking and reporting --- submodules/mh_stuff | 2 +- .../SetupFlow/UpdateCheckPage.cpp | 169 ++++++++---- tf2_bot_detector/UpdateManager.cpp | 258 ++++++++++++------ tf2_bot_detector/UpdateManager.h | 14 +- 4 files changed, 293 insertions(+), 150 deletions(-) diff --git a/submodules/mh_stuff b/submodules/mh_stuff index 79348bf0..f2c38877 160000 --- a/submodules/mh_stuff +++ b/submodules/mh_stuff @@ -1 +1 @@ -Subproject commit 79348bf042562741fef4560e71fd275200d96071 +Subproject commit f2c388779e950b1f524b60becad357d4be902387 diff --git a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp index 824f7805..f5c48967 100644 --- a/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp +++ b/tf2_bot_detector/SetupFlow/UpdateCheckPage.cpp @@ -7,6 +7,7 @@ #include "UpdateManager.h" #include +#include using namespace tf2_bot_detector; @@ -26,7 +27,7 @@ namespace bool WantsContinueButton() const override { return false; } private: - UpdateStatus m_LastUpdateStatus = UpdateStatus::Unknown; + mh::status_reader m_StatusReader; bool m_HasChangedReleaseChannel = false; bool m_HasCheckedForUpdate = false; bool m_UpdateButtonPressed = false; @@ -48,7 +49,11 @@ namespace { m_HasCheckedForUpdate = true; - const UpdateStatus updateStatus = m_LastUpdateStatus = ds.m_UpdateManager->GetUpdateStatus(); + if (!m_StatusReader.has_value()) + m_StatusReader = ds.m_UpdateManager->GetUpdateStatus(); + + const auto updateStatus = m_StatusReader.get(); + const IAvailableUpdate* update = ds.m_UpdateManager->GetAvailableUpdate(); ImGui::TextFmt("Update Check"); @@ -74,53 +79,69 @@ namespace } continueButtonMode = ContinueButtonMode::None; - switch (updateStatus) + // Early out + switch (updateStatus.m_Status) { - case UpdateStatus::Unknown: - ImGui::TextFmt({ 1, 1, 0, 1 }, "Unknown"); - continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; - break; - case UpdateStatus::UpdateCheckDisabled: { - ImGui::TextFmt("Automatic update checks disabled by user"); if (!m_HasChangedReleaseChannel) return OnDrawResult::EndDrawing; - ImGui::NewLine(); - continueButtonMode = ContinueButtonMode::Continue; break; } + case UpdateStatus::InternetAccessDisabled: - ImGui::TextFmt("Internet connectivity disabled by user"); return OnDrawResult::EndDrawing; - case UpdateStatus::CheckQueued: - ImGui::TextFmt("Update check queued..."); - break; - case UpdateStatus::Checking: - ImGui::TextFmt("Checking for updates..."); + default: break; + } + // Message color + ImVec4 msgColor = { 1, 0, 1, 1 }; + switch (updateStatus.m_Status) + { + // Red + case UpdateStatus::StateSwitchFailure: case UpdateStatus::CheckFailed: - ImGui::TextFmt({ 1, 1, 0, 1 }, "Update check failed"); - continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; - ImGui::NewLine(); + case UpdateStatus::UpdateToolDownloadFailed: + case UpdateStatus::DownloadFailed: + case UpdateStatus::UpdateFailed: + msgColor = { 1, 0, 0, 1 }; break; + + // Green case UpdateStatus::UpToDate: - { - ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, - mh::enum_fmt(ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public))); + case UpdateStatus::UpdateSuccess: + msgColor = { 0, 1, 0, 1 }; + break; - if (!m_HasChangedReleaseChannel) - return OnDrawResult::EndDrawing; + // Cyan + case UpdateStatus::UpdateAvailable: + msgColor = { 0, 1, 1, 1 }; + break; - ImGui::NewLine(); - continueButtonMode = ContinueButtonMode::Continue; + // Yellow + case UpdateStatus::Unknown: + case UpdateStatus::UpdateCheckDisabled: + case UpdateStatus::InternetAccessDisabled: + msgColor = { 1, 1, 0, 1 }; + break; + // White + case UpdateStatus::CheckQueued: + case UpdateStatus::Checking: + case UpdateStatus::UpdateToolRequired: + case UpdateStatus::UpdateToolDownloading: + case UpdateStatus::UpdateToolDownloadSuccess: + case UpdateStatus::Downloading: + case UpdateStatus::DownloadSuccess: + case UpdateStatus::Updating: + msgColor = { 1, 1, 1, 1 }; break; } - case UpdateStatus::UpdateAvailable: + + if (updateStatus.m_Status == UpdateStatus::UpdateAvailable) { ImGui::TextFmt({ 0, 1, 1, 1 }, "Update available: v{} {:v} (current version v{})", update->m_BuildInfo.m_Version, mh::enum_fmt(update->m_BuildInfo.m_ReleaseChannel), VERSION); @@ -150,51 +171,75 @@ namespace }, "Unable to determine GitHub URL"); ImGui::SameLine(); - continueButtonMode = ContinueButtonMode::ContinueWithoutUpdating; - break; + } + else if (updateStatus.m_Status == UpdateStatus::Unknown) + { + ImGui::TextFmt(msgColor, "UNKNOWN UPDATE STATUS: {}", std::quoted(updateStatus.m_Message)); + } + else + { + assert(!updateStatus.m_Message.empty()); + ImGui::TextFmt(msgColor, updateStatus.m_Message); } - case UpdateStatus::Updating: - ImGui::TextFmt("Updating..."); - break; +#if 0 + switch (updateStatus.m_Status) + { + case UpdateStatus::UpToDate: + { + ImGui::TextFmt({ 0, 1, 0, 1 }, "Up to date (v{} {:v})", VERSION, + mh::enum_fmt(ds.m_Settings->m_ReleaseChannel.value_or(ReleaseChannel::Public))); + + if (!m_HasChangedReleaseChannel) + return OnDrawResult::EndDrawing; + + ImGui::NewLine(); + continueButtonMode = ContinueButtonMode::Continue; - case UpdateStatus::UpdateFailed: - ImGui::TextFmt({ 1, 0, 0, 1 }, "Update failed"); - break; - case UpdateStatus::UpdateSuccess: - ImGui::TextFmt({ 0, 1, 0, 1 }, "Update succeeded. Restart TF2 Bot Detector to apply."); break; + } + } +#endif + + // Continue button mode + switch (updateStatus.m_Status) + { + case UpdateStatus::Unknown: + case UpdateStatus::CheckFailed: + case UpdateStatus::StateSwitchFailure: + case UpdateStatus::UpdateAvailable: + { + if (ImGui::Button("Continue without updating >")) + return OnDrawResult::EndDrawing; - case UpdateStatus::Downloading: - ImGui::TextFmt("Downloading update..."); break; + } + + case UpdateStatus::UpToDate: + case UpdateStatus::UpdateCheckDisabled: + case UpdateStatus::InternetAccessDisabled: + case UpdateStatus::UpdateSuccess: + case UpdateStatus::UpdateToolDownloadFailed: case UpdateStatus::DownloadFailed: - ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update."); - break; - case UpdateStatus::DownloadSuccess: - ImGui::TextFmt("Update download complete."); + case UpdateStatus::UpdateFailed: + { + if (ImGui::Button("Continue >")) + return OnDrawResult::EndDrawing; + break; + } + case UpdateStatus::CheckQueued: + case UpdateStatus::Checking: case UpdateStatus::UpdateToolRequired: case UpdateStatus::UpdateToolDownloading: - ImGui::TextFmt("Downloading updater..."); - break; - case UpdateStatus::UpdateToolDownloadingFailed: - ImGui::TextFmt({ 1, 0, 0, 1 }, "Failed to download update tool."); + case UpdateStatus::UpdateToolDownloadSuccess: + case UpdateStatus::Downloading: + case UpdateStatus::DownloadSuccess: + case UpdateStatus::Updating: break; } - if (continueButtonMode == ContinueButtonMode::ContinueWithoutUpdating && - ImGui::Button("Continue without updating >")) - { - return OnDrawResult::EndDrawing; - } - if (continueButtonMode == ContinueButtonMode::Continue && - ImGui::Button("Continue >")) - { - return OnDrawResult::EndDrawing; - } - return OnDrawResult::ContinueDrawing; } @@ -209,9 +254,13 @@ namespace if (!m_HasCheckedForUpdate) return false; - return mh::none_eq(m_LastUpdateStatus + return mh::none_eq(m_StatusReader.get().m_Status , UpdateStatus::CheckQueued , UpdateStatus::Checking + , UpdateStatus::UpdateToolRequired + , UpdateStatus::UpdateToolDownloading + , UpdateStatus::Downloading + , UpdateStatus::DownloadSuccess , UpdateStatus::Updating ); } diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 3900b6c4..d592e850 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -108,8 +108,7 @@ namespace UpdateManager(const Settings& settings); void Update() override; - UpdateStatus GetUpdateStatus() const override; - std::string GetErrorString() const override; + mh::status_reader GetUpdateStatus() const override { return m_State.GetUpdateStatus(); } const AvailableUpdate* GetAvailableUpdate() const override; void QueueUpdateCheck() override { m_IsUpdateQueued = true; } @@ -148,37 +147,84 @@ namespace { bool m_Success{}; }; - struct UpdateToolExceptionData : BaseExceptionData - { - using BaseExceptionData::BaseExceptionData; - }; - - using UpdateCheckState_t = std::variant, AvailableUpdate, ExceptionData>; - UpdateCheckState_t m_UpdateCheckState; using State_t = std::variant< std::monostate, std::future, DownloadedBuild, - ExceptionData, std::future, InstallUpdate::Result, - ExceptionData, std::future, DownloadedUpdateTool, - ExceptionData, std::future, - UpdateToolResult, - ExceptionData + UpdateToolResult >; - State_t m_State; + using UpdateCheckState_t = std::variant, AvailableUpdate>; + + struct StateManager + { + const State_t& GetVariant() const { return m_Variant; } + UpdateCheckState_t& GetUpdateCheckVariant() { return m_UpdateCheckVariant; } + const UpdateCheckState_t& GetUpdateCheckVariant() const { return m_UpdateCheckVariant; } + + template + void Emplace(const mh::source_location& location, UpdateStatus status, std::string msg, TArgs&&... args) + { + SetUpdateStatus(location, status, std::move(msg)); + m_Variant.emplace(std::forward(args)...); + } + + void Clear(const mh::source_location& location, UpdateStatus status, std::string msg) + { + Emplace(location, status, std::move(msg)); + } + void ClearUpdateCheck(const mh::source_location& location, UpdateStatus status, std::string msg) + { + SetUpdateStatus(location, status, std::move(msg)); + m_UpdateCheckVariant.emplace<0>(); + } + + template + void Set(const mh::source_location& location, UpdateStatus status, std::string msg, T&& value) + { + SetUpdateStatus(location, status, std::move(msg)); + m_Variant = std::forward(value); + } + + template + void SetUpdateCheck(const mh::source_location& location, UpdateStatus status, std::string msg, T&& value) + { + SetUpdateStatus(location, status, std::move(msg)); + m_UpdateCheckVariant.emplace(std::forward(value)); + } + + void SetUpdateStatus(const mh::source_location& location, UpdateStatus status, + const std::string_view& msg); + mh::status_reader GetUpdateStatus() const { return m_UpdateStatus; } - template bool UpdateFutureState(); - template bool UpdateFutureStates(); + template bool Update(const mh::source_location& location, + UpdateStatus success, const std::string_view& successMsg, + UpdateStatus failure, const std::string_view& failureMsg) + { + return UpdateVariant(m_Variant, location, + success, successMsg, failure, failureMsg); + } + + private: + template + bool UpdateVariant(TVariant& variant, const mh::source_location& location, + UpdateStatus success, const std::string_view& successMsg, + UpdateStatus failure, const std::string_view& failureMsg); + + UpdateCheckState_t m_UpdateCheckVariant; + State_t m_Variant; + mh::status_source m_UpdateStatus; + + } m_State; bool CanReplaceUpdateCheckState() const; @@ -201,20 +247,27 @@ namespace { } - template - bool UpdateManager::UpdateFutureState() + template + bool UpdateManager::StateManager::UpdateVariant(TVariant& variant, const mh::source_location& location, + UpdateStatus success, const std::string_view& successMsg, + UpdateStatus failure, const std::string_view& failureMsg) { - if (auto future = std::get_if>(&m_State)) + if (auto future = std::get_if>(&variant)) { try { if (mh::is_future_ready(*future)) - m_State.emplace(future->get()); + { + auto value = future->get(); + SetUpdateStatus(MH_SOURCE_LOCATION_CURRENT(), success, std::string(successMsg)); + variant.emplace(std::move(value)); + } } catch (const std::exception& e) { LogException(MH_SOURCE_LOCATION_CURRENT(), e, __FUNCSIG__); - m_State.emplace>(typeid(e), e.what(), std::current_exception()); + Clear(MH_SOURCE_LOCATION_CURRENT(), failure, + mh::format("{}:\n\t- {}\n\t- {}", failureMsg, typeid(e).name(), e.what())); } return true; @@ -223,12 +276,6 @@ namespace return false; } - template - bool UpdateManager::UpdateFutureStates() - { - return (... || UpdateFutureState()); - } - void UpdateManager::Update() { if (m_IsUpdateQueued && CanReplaceUpdateCheckState()) @@ -238,25 +285,27 @@ namespace if (client && (releaseChannel != ReleaseChannel::None)) { auto sharedClient = client->shared_from_this(); - m_UpdateCheckState = std::async([sharedClient, releaseChannel]() -> BuildInfo - { - const mh::fmtstr<256> url( - "https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.json?type={:v}", - mh::enum_fmt(releaseChannel)); - DebugLog("HTTP GET {}", url); - auto response = sharedClient->GetString(url.view()); + m_State.SetUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Checking, "Checking for updates...", + std::async([sharedClient, releaseChannel]() -> BuildInfo + { + const mh::fmtstr<256> url( + "https://tf2bd-util.pazer.us/AppInstaller/LatestVersion.json?type={:v}", + mh::enum_fmt(releaseChannel)); - auto json = nlohmann::json::parse(response); + DebugLog("HTTP GET {}", url); + auto response = sharedClient->GetString(url.view()); - return json.get(); - }); + auto json = nlohmann::json::parse(response); + + return json.get(); + })); m_IsUpdateQueued = false; } } - if (auto future = std::get_if>(&m_UpdateCheckState)) + if (auto future = std::get_if>(&m_State.GetUpdateCheckVariant())) { try { @@ -264,38 +313,59 @@ namespace if (client) { if (mh::is_future_ready(*future)) - m_UpdateCheckState.emplace(*client, *this, future->get()); + { + AvailableUpdate update(*client, *this, future->get()); + + if (update.m_BuildInfo.m_Version <= VERSION) + { + m_State.SetUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpToDate, + mh::format("Up to date (v{} {:v})", VERSION, mh::enum_fmt( + m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::Public))), + std::move(update)); + } + else + { + m_State.SetUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateAvailable, + mh::format("Update available (v{} {:v})", update.m_BuildInfo.m_Version, mh::enum_fmt( + m_Settings.m_ReleaseChannel.value_or(ReleaseChannel::Public))), + std::move(update)); + } + } } else { - LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable, cancelling update"); - m_UpdateCheckState.emplace<0>(); + m_State.ClearUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::CheckFailed, + "Update check failed: HTTPClient unavailable"); } } catch (const std::exception& e) { - LogException(MH_SOURCE_LOCATION_CURRENT(), e, __FUNCSIG__); - m_UpdateCheckState.emplace>(typeid(e), e.what(), std::current_exception()); + m_State.ClearUpdateCheck(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::CheckFailed, + mh::format("Update check failed:\n\t- {}\n\t- {}", typeid(e).name(), e.what())); } } - if (auto downloadedBuild = std::get_if(&m_State)) + if (auto downloadedBuild = std::get_if(&m_State.GetVariant())) { [&] { auto client = m_Settings.GetHTTPClient(); if (!client) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: HTTPClient unavailable"); return; } - m_State = DownloadUpdateTool(*client, downloadedBuild->m_UpdaterVariant, - mh::format("--update-type Portable --source-path {} --dest-path {}", - downloadedBuild->m_ExtractedLocation, Platform::GetCurrentExeDir())); + m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateToolDownloading, + "New version downloaded. Downloading update tool...", + + DownloadUpdateTool(*client, downloadedBuild->m_UpdaterVariant, + mh::format("--update-type Portable --source-path {} --dest-path {}", + downloadedBuild->m_ExtractedLocation, Platform::GetCurrentExeDir()))); }(); } - else if (auto installUpdateResult = std::get_if(&m_State)) + else if (auto installUpdateResult = std::get_if(&m_State.GetVariant())) { [&] { @@ -306,34 +376,58 @@ namespace auto availableUpdate = GetAvailableUpdate(); if (!availableUpdate) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "availableUpdate was nullptr"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: availableUpdate was nullptr"); return; } if (!availableUpdate->m_Updater) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "availableUpdate->m_Updater was nullptr"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: availableUpdate->m_Updater was nullptr"); return; } auto client = m_Settings.GetHTTPClient(); if (!client) { - LogError(MH_SOURCE_LOCATION_CURRENT(), "HTTPClient unavailable"); + m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Unable to begin downloading update tool: HTTPClient unavailable"); return; } - m_State = DownloadUpdateTool(*client, *availableUpdate->m_Updater, needsUpdateTool->m_UpdateToolArgs); + m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateToolDownloading, + "Platform app updater unavailable. Downloading update tool...", + + DownloadUpdateTool(*client, *availableUpdate->m_Updater, needsUpdateTool->m_UpdateToolArgs)); }(); } - else if (auto downloadedUpdateTool = std::get_if(&m_State)) + else if (auto downloadedUpdateTool = std::get_if(&m_State.GetVariant())) { - m_State = RunUpdateTool(downloadedUpdateTool->m_Path, downloadedUpdateTool->m_Arguments); + m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Updating, + "Running update tool...", + + RunUpdateTool(downloadedUpdateTool->m_Path, downloadedUpdateTool->m_Arguments)); } - UpdateFutureStates(); + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::DownloadSuccess, "Finished downloading new version.", + UpdateStatus::DownloadFailed, "Failed to download new version."); + + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::UpdateSuccess, "Finished running platform update.", + UpdateStatus::UpdateFailed, "Failed to run platform update."); + + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::UpdateToolDownloadSuccess, "Finished downloading update tool.", + UpdateStatus::UpdateToolDownloadFailed, "Failed to download update tool."); + + m_State.Update(MH_SOURCE_LOCATION_CURRENT(), + UpdateStatus::UpdateSuccess, "Update complete.", + UpdateStatus::UpdateFailed, "Update failed."); } +#if 0 UpdateStatus UpdateManager::GetUpdateStatus() const { if (m_IsUpdateQueued) @@ -427,31 +521,11 @@ namespace m_UpdateCheckState.index(), m_State.index()); return UpdateStatus::Unknown; } - - std::string UpdateManager::GetErrorString() const - { - return std::visit([](const auto& value) -> std::string - { - using type_t = std::decay_t; - if constexpr (std::is_base_of_v) - { - const BaseExceptionData& d = value; - return mh::format("{}: {}"sv, d.m_Type.name(), d.m_Message); - } - else - { - return {}; - } - - }, m_State); - } +#endif auto UpdateManager::GetAvailableUpdate() const -> const AvailableUpdate* { - if (GetUpdateStatus() == UpdateStatus::UpdateAvailable) - return std::get_if(&m_UpdateCheckState); - - return nullptr; + return std::get_if(&m_State.GetUpdateCheckVariant()); } /// @@ -459,7 +533,7 @@ namespace /// bool UpdateManager::CanReplaceUpdateCheckState() const { - const auto* future = std::get_if>(&m_UpdateCheckState); + const auto* future = std::get_if>(&m_State.GetUpdateCheckVariant()); if (!future) return true; // some other value in the variant @@ -683,20 +757,27 @@ namespace { if (Platform::CanInstallUpdate(m_BuildInfo)) { - Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed and able to install update"); - m_Parent.m_State = Platform::BeginInstallUpdate(m_BuildInfo, *client); + m_Parent.m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Updating, + "Platform reports that TF2 Bot Detector is already installed, and it can be updated. Running platform updater...", + + Platform::BeginInstallUpdate(m_BuildInfo, *client)); + return true; } else { - LogError(MH_SOURCE_LOCATION_CURRENT(), "Platform reports being installed but cannot install update"); + m_Parent.m_State.Clear(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::UpdateFailed, + "Platform reports that TF2 Bot Detector is installed, but it is unable to install updates."); return false; } } else { - Log(MH_SOURCE_LOCATION_CURRENT(), "Platform reports *not* being installed. Running portable update."); - m_Parent.m_State = DownloadBuild(*client, *m_Portable, *m_Updater); + m_Parent.m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Downloading, + "Platform reports that TF2 Bot Detector is not installed. Updating in-place (portable mode)", + + DownloadBuild(*client, *m_Portable, *m_Updater)); + return true; } } @@ -706,6 +787,13 @@ namespace m_Type(type), m_Message(std::move(message)), m_Exception(exception) { } + + void UpdateManager::StateManager::SetUpdateStatus( + const mh::source_location& location, UpdateStatus status, const std::string_view& msg) + { + if (m_UpdateStatus.set(status, msg)) + DebugLog(location, "{}: {}", mh::enum_fmt(status), msg); + } } std::unique_ptr tf2_bot_detector::IUpdateManager::Create(const Settings& settings) diff --git a/tf2_bot_detector/UpdateManager.h b/tf2_bot_detector/UpdateManager.h index 245338c3..87d2e866 100644 --- a/tf2_bot_detector/UpdateManager.h +++ b/tf2_bot_detector/UpdateManager.h @@ -4,6 +4,7 @@ #include "ReleaseChannel.h" #include "Version.h" +#include #include #include @@ -50,6 +51,8 @@ namespace tf2_bot_detector { Unknown = 0, + StateSwitchFailure, + UpdateCheckDisabled, InternetAccessDisabled, @@ -62,7 +65,8 @@ namespace tf2_bot_detector UpdateToolRequired, UpdateToolDownloading, - UpdateToolDownloadingFailed, + UpdateToolDownloadFailed, + UpdateToolDownloadSuccess, Downloading, DownloadFailed, @@ -84,8 +88,7 @@ namespace tf2_bot_detector virtual void Update() = 0; - virtual UpdateStatus GetUpdateStatus() const = 0; - virtual std::string GetErrorString() const = 0; + virtual mh::status_reader GetUpdateStatus() const = 0; virtual const IAvailableUpdate* GetAvailableUpdate() const = 0; }; } @@ -93,6 +96,8 @@ namespace tf2_bot_detector MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(Unknown) + MH_ENUM_REFLECT_VALUE(StateSwitchFailure) + MH_ENUM_REFLECT_VALUE(UpdateCheckDisabled) MH_ENUM_REFLECT_VALUE(InternetAccessDisabled) @@ -104,7 +109,8 @@ MH_ENUM_REFLECT_BEGIN(tf2_bot_detector::UpdateStatus) MH_ENUM_REFLECT_VALUE(UpdateToolRequired) MH_ENUM_REFLECT_VALUE(UpdateToolDownloading) - MH_ENUM_REFLECT_VALUE(UpdateToolDownloadingFailed) + MH_ENUM_REFLECT_VALUE(UpdateToolDownloadFailed) + MH_ENUM_REFLECT_VALUE(UpdateToolDownloadSuccess) MH_ENUM_REFLECT_VALUE(Downloading) MH_ENUM_REFLECT_VALUE(DownloadFailed) From 74b794223fb18b6ae8ebae2df9007208a719e9f2 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Mon, 7 Sep 2020 23:20:53 -0700 Subject: [PATCH 153/161] Fixed some issues with the "missing microsoft store" updater path. --- .../Platform/Windows/PlatformInstall.cpp | 2 +- tf2_bot_detector_updater/Common.h | 3 +- tf2_bot_detector_updater/Update_MSIX.cpp | 4 +- tf2_bot_detector_updater/main.cpp | 37 ++++++------------- 4 files changed, 16 insertions(+), 30 deletions(-) diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index b9d9918d..047b73bf 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -104,7 +104,7 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "LaunchUriAsync returned false, requesting update tool"); return InstallUpdate::NeedsUpdateTool { - .m_UpdateToolArgs = mh::format("--msix-bundle-url {}", std::quoted(bundleUri)), + .m_UpdateToolArgs = mh::format("--update-type MSIX --source-path {}", std::quoted(bundleUri)), }; } }); diff --git a/tf2_bot_detector_updater/Common.h b/tf2_bot_detector_updater/Common.h index 17708713..fffa2d54 100644 --- a/tf2_bot_detector_updater/Common.h +++ b/tf2_bot_detector_updater/Common.h @@ -29,9 +29,8 @@ namespace tf2_bot_detector::Updater bool m_PauseOnError = true; UpdateType m_UpdateType = UpdateType::Unknown; - ReleaseChannel m_ReleaseChannel = ReleaseChannel::None; - std::filesystem::path m_SourcePath; + std::string m_SourcePath; std::filesystem::path m_DestPath; }; diff --git a/tf2_bot_detector_updater/Update_MSIX.cpp b/tf2_bot_detector_updater/Update_MSIX.cpp index 270ca727..377189a2 100644 --- a/tf2_bot_detector_updater/Update_MSIX.cpp +++ b/tf2_bot_detector_updater/Update_MSIX.cpp @@ -2,6 +2,7 @@ #include "Common.h" #include +#include #include #include @@ -27,8 +28,7 @@ int tf2_bot_detector::Updater::Update_MSIX() try { std::cerr << "Attempting to install via API..." << std::endl; - const auto mainBundleURL = mh::format(L"https://tf2bd-util.pazer.us/AppInstaller/{:v}.msixbundle", - mh::enum_fmt(s_CmdLineArgs.m_ReleaseChannel)); + const auto mainBundleURL = mh::change_encoding(s_CmdLineArgs.m_SourcePath); std::wcerr << "Main bundle URL: " << mainBundleURL; const Uri uri = mainBundleURL; diff --git a/tf2_bot_detector_updater/main.cpp b/tf2_bot_detector_updater/main.cpp index 80f56938..4c6162fd 100644 --- a/tf2_bot_detector_updater/main.cpp +++ b/tf2_bot_detector_updater/main.cpp @@ -21,6 +21,12 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; [[nodiscard]] static int ValidateParameters() { + const auto SourcePathEmpty = [] + { + std::cerr << "Source path not specified. Use --source-path " << std::endl; + return 1; + }; + if (s_CmdLineArgs.m_UpdateType == UpdateType::Unknown) { std::cerr << "Update type not specified. Use --update-type with one of the following values:\n" @@ -38,25 +44,11 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; return 1; } - if (mh::any_eq(s_CmdLineArgs.m_UpdateType, UpdateType::MSIX)) - { - if (s_CmdLineArgs.m_ReleaseChannel == ReleaseChannel::None) - { - std::cerr << mh::format("Release channel not specified. Use --release-channel <{:v}|{:v}|{:v}>", - mh::enum_fmt(ReleaseChannel::Public), - mh::enum_fmt(ReleaseChannel::Preview), - mh::enum_fmt(ReleaseChannel::Nightly)) - << std::endl; - return 1; - } - } - if (s_CmdLineArgs.m_UpdateType == UpdateType::Portable) { if (s_CmdLineArgs.m_SourcePath.empty()) { - std::cerr << "Source path not specified. Use --source-path " << std::endl; - return 1; + return SourcePathEmpty(); } if (s_CmdLineArgs.m_DestPath.empty()) { @@ -64,6 +56,11 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; return 1; } } + else if (s_CmdLineArgs.m_UpdateType == UpdateType::MSIX) + { + if (s_CmdLineArgs.m_SourcePath.empty()) + return SourcePathEmpty(); + } return 0; } @@ -84,16 +81,6 @@ CmdLineArgs tf2_bot_detector::Updater::s_CmdLineArgs; s_CmdLineArgs.m_UpdateType = mh::enum_type::find_value(argv[i + 1]); } - else if (arg == "--release-channel") - { - if (!hasNextArg) - { - std::cerr << "Missing argument for " << std::quoted(arg) << std::endl; - return 1; - } - - s_CmdLineArgs.m_ReleaseChannel = mh::enum_type::find_value(argv[i + 1]); - } else if (arg == "--source-path") { if (!hasNextArg) From f0ac1e85844e3c856ab38b652a255a83c4f20df9 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 8 Sep 2020 09:11:54 -0700 Subject: [PATCH 154/161] re-enabled appinstaller update path. --- tf2_bot_detector/Platform/Windows/PlatformInstall.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index 047b73bf..f1336f60 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -88,7 +88,6 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat return std::async([bundleUri]() -> InstallUpdate::Result { -#if 0 const Uri uri = mh::format(L"ms-appinstaller:?source={}", ToWC(bundleUri)); DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Attempting to LaunchUriAsync for {}", ToMB(uri.ToString())); @@ -99,7 +98,6 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat return InstallUpdate::StartedNoFeedback{}; } else -#endif { DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "LaunchUriAsync returned false, requesting update tool"); return InstallUpdate::NeedsUpdateTool From 6d414a1a5b89cee7c19b178204c6af037236dfaf Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 8 Sep 2020 10:21:52 -0700 Subject: [PATCH 155/161] Attempt to work around broken appinstaller launch on LTSC --- tf2_bot_detector/Platform/Windows/PlatformInstall.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index f1336f60..06b582ed 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -83,6 +83,7 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat using winrt::Windows::ApplicationModel::Package; using winrt::Windows::Foundation::Uri; using winrt::Windows::System::Launcher; + using winrt::Windows::System::LauncherOptions; const auto bundleUri = buildInfo.m_MSIXBundleURL; @@ -91,7 +92,14 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat const Uri uri = mh::format(L"ms-appinstaller:?source={}", ToWC(bundleUri)); DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Attempting to LaunchUriAsync for {}", ToMB(uri.ToString())); - auto result = Launcher::LaunchUriAsync(uri); + LauncherOptions options; + + DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Existing value of DisplayApplicationPicker: {}", + options.DisplayApplicationPicker()); + options.DisplayApplicationPicker(false); + options.TargetApplicationPackageFamilyName(L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + + auto result = Launcher::LaunchUriAsync(uri, options); if (result.get()) { From 481b986ee85e57743a6c317d34b950d0f0ff0856 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 8 Sep 2020 10:43:34 -0700 Subject: [PATCH 156/161] Switched back to using the update tool in all scenarios, since LaunchUriAsync doesn't actually report failure properly. --- tf2_bot_detector/Platform/Windows/PlatformInstall.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp index 06b582ed..d709a552 100644 --- a/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp +++ b/tf2_bot_detector/Platform/Windows/PlatformInstall.cpp @@ -89,6 +89,7 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat return std::async([bundleUri]() -> InstallUpdate::Result { +#if 0 const Uri uri = mh::format(L"ms-appinstaller:?source={}", ToWC(bundleUri)); DebugLog(MH_SOURCE_LOCATION_CURRENT(), "Attempting to LaunchUriAsync for {}", ToMB(uri.ToString())); @@ -106,8 +107,11 @@ std::future tf2_bot_detector::Platform::BeginInstallUpdat return InstallUpdate::StartedNoFeedback{}; } else +#endif { - DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), "LaunchUriAsync returned false, requesting update tool"); + DebugLogWarning(MH_SOURCE_LOCATION_CURRENT(), + "Microsoft has ensured that we can't have anything nice, requesting update tool"); + return InstallUpdate::NeedsUpdateTool { .m_UpdateToolArgs = mh::format("--update-type MSIX --source-path {}", std::quoted(bundleUri)), From 15b5792b0c8426b384786d1336d92b9ca8a53323 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Tue, 8 Sep 2020 22:37:39 -0700 Subject: [PATCH 157/161] Fixed a typo in the update channel dropdown. --- tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp index fec7be14..b2d60ed5 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp @@ -525,7 +525,7 @@ bool tf2_bot_detector::Combo(const char* label_id, std::optional { const char* friendlyText = ""; static constexpr char FRIENDLY_TEXT_DISABLED[] = "Disable automatic update checks"; - static constexpr char FRIENDLY_TEXT_NIGHTLY[] = "Notify about new every builds (unstable)"; + static constexpr char FRIENDLY_TEXT_NIGHTLY[] = "Notify about every new builds (unstable)"; static constexpr char FRIENDLY_TEXT_PREVIEW[] = "Notify about new preview releases"; static constexpr char FRIENDLY_TEXT_STABLE[] = "Notify about new stable releases"; From f817f0d7a107fbb48d97487afcbfd80cbce97062 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 9 Sep 2020 11:33:55 -0700 Subject: [PATCH 158/161] Send the run id to tf2bd-util so we can generate a github link for nightly builds. --- .github/workflows/ccpp.yml | 4 ++++ tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 8c3e28cb..14f7b814 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -33,6 +33,10 @@ jobs: data: ${{ env.TF2BD_VERSION }} variable: TF2BD_VERSION + - name: Set TF2BD version link on tf2bd-util + run: | + curl -X POST --data-urlencode "key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}" --data-urlencode "version=${{ env.TF2BD_VERSION }}" --data-urlencode "runid=${{ github.run_id }}" "https://tf2bd-util.pazer.us/NightlyArchive/SetData/GitHubRunID" + build: diff --git a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp index b2d60ed5..64652128 100644 --- a/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp +++ b/tf2_bot_detector/UI/ImGui_TF2BotDetector.cpp @@ -525,7 +525,7 @@ bool tf2_bot_detector::Combo(const char* label_id, std::optional { const char* friendlyText = ""; static constexpr char FRIENDLY_TEXT_DISABLED[] = "Disable automatic update checks"; - static constexpr char FRIENDLY_TEXT_NIGHTLY[] = "Notify about every new builds (unstable)"; + static constexpr char FRIENDLY_TEXT_NIGHTLY[] = "Notify about every new build (unstable)"; static constexpr char FRIENDLY_TEXT_PREVIEW[] = "Notify about new preview releases"; static constexpr char FRIENDLY_TEXT_STABLE[] = "Notify about new stable releases"; From 6575c33eb6d3075aa8d3d3edc36f6bf73f2c5c12 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 9 Sep 2020 11:36:51 -0700 Subject: [PATCH 159/161] Apparently --data-urlencode doesn't work after all. --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 14f7b814..644e7b86 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -35,7 +35,7 @@ jobs: - name: Set TF2BD version link on tf2bd-util run: | - curl -X POST --data-urlencode "key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}" --data-urlencode "version=${{ env.TF2BD_VERSION }}" --data-urlencode "runid=${{ github.run_id }}" "https://tf2bd-util.pazer.us/NightlyArchive/SetData/GitHubRunID" + curl -X POST "https://tf2bd-util.pazer.us/NightlyArchive/SetData/GitHubRunID?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&runid=${{ github.run_id }}" From 7039f225f8c1f58b2e7d1918e0ac630c83a76a5e Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 9 Sep 2020 11:44:12 -0700 Subject: [PATCH 160/161] Fix curl sending a request without a content-length header. --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 644e7b86..3657dcaf 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -35,7 +35,7 @@ jobs: - name: Set TF2BD version link on tf2bd-util run: | - curl -X POST "https://tf2bd-util.pazer.us/NightlyArchive/SetData/GitHubRunID?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&runid=${{ github.run_id }}" + curl -X POST "https://tf2bd-util.pazer.us/NightlyArchive/SetData/GitHubRunID?key=${{ secrets.TF2BD_NIGHTLY_UPLOAD_API_KEY }}&version=${{ env.TF2BD_VERSION }}&runid=${{ github.run_id }}" -d "" From a0e825e8aa5e049bac0506d4d863249bc34e3299 Mon Sep 17 00:00:00 2001 From: Matt Haynie Date: Wed, 9 Sep 2020 12:04:04 -0700 Subject: [PATCH 161/161] Cleared up a few of the more confusing update messages. --- tf2_bot_detector/UpdateManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tf2_bot_detector/UpdateManager.cpp b/tf2_bot_detector/UpdateManager.cpp index 0f8315a3..269351b6 100644 --- a/tf2_bot_detector/UpdateManager.cpp +++ b/tf2_bot_detector/UpdateManager.cpp @@ -697,7 +697,7 @@ namespace }); m_Parent.m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Updating, - "Platform reports that TF2 Bot Detector is already installed, and it can be updated. Running platform updater...", + "Installing platform update...", std::move(future)); return true; @@ -716,7 +716,7 @@ namespace auto downloadBuildFuture = DownloadBuild(*client, portable, updater); m_Parent.m_State.Set(MH_SOURCE_LOCATION_CURRENT(), UpdateStatus::Downloading, - "Platform reports that TF2 Bot Detector is not installed. Updating in-place (portable mode)", + "Downloading new build...", std::move(downloadBuildFuture)); return true;