Compare commits

...
Author SHA1 Message Date
dependabot[bot] 6396afe2e3 Bump Microsoft.Windows.CsWin32 from 0.3.335 to 0.3.363 (#738)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.363
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 02:24:49 +00:00
dependabot[bot] 16a95c1bfd Bump QRCoder from 1.8.0 to 1.8.1 (#739)
---
updated-dependencies:
- dependency-name: QRCoder
  dependency-version: 1.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-14 02:24:15 +00:00
dependabot[bot] 989f37b1fb Bump protobuf-net from 3.4.21 to 3.4.30 (#735)
---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-version: 3.4.30
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 21:27:47 +00:00
dependabot[bot] 8275f16d3e Bump Microsoft.Windows.CsWin32 from 0.3.333 to 0.3.335 (#734)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.335
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-21 21:27:38 +00:00
dependabot[bot] e7474cb9ee Bump Microsoft.Windows.CsWin32 from 0.3.321 to 0.3.333 (#732)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.333
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-12 15:14:40 +00:00
dependabot[bot] c124842e86 Bump protobuf-net from 3.4.14 to 3.4.21 (#731)
---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-version: 3.4.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-31 21:26:48 +00:00
dependabot[bot] b1557786b2 Bump protobuf-net from 3.3.21 to 3.4.14 (#730)
---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-version: 3.4.14
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 19:52:28 +00:00
dependabot[bot] ae192f6448 Bump Microsoft.Windows.CsWin32 from 0.3.298 to 0.3.321 (#728)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.321
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 21:35:25 +00:00
dependabot[bot] 48b0f51c53 Bump protobuf-net from 3.2.56 to 3.3.21 (#729)
---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-version: 3.3.21
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-17 21:35:15 +00:00
dependabot[bot] 1e8e20c72c Bump actions/setup-dotnet from 5 to 6 (#726)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5 to 6.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 21:21:04 +00:00
dependabot[bot] c5fc766986 Bump actions/checkout from 6 to 7 (#722)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 21:35:32 +00:00
dependabot[bot] d178832b38 Bump Microsoft.Windows.CsWin32 from 0.3.296 to 0.3.298 (#723)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.298
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 21:35:02 +00:00
dependabot[bot] b2b7e975ad Bump Microsoft.Windows.CsWin32 from 0.3.275 to 0.3.296 (#720)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.296
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 21:29:46 +00:00
Yaakov 91d623419a Merge pull request #717 from shelvacu-forks/patch-update-steamkit 2026-06-01 08:47:48 +10:00
Shelvacu 282b9eae81 Update SteamKit2 to 3.4.0 2026-05-29 21:03:37 -07:00
dependabot[bot] 66c63c6dad Bump Microsoft.Windows.CsWin32 from 0.3.269 to 0.3.275 (#703)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.275
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-25 14:28:59 +00:00
dependabot[bot] 3dc34b50bd Bump softprops/action-gh-release from 2 to 3 (#702)
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2 to 3.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v2...v3)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 22:15:18 +00:00
dependabot[bot] 4aac37401c Bump QRCoder from 1.7.0 to 1.8.0 (#700)
---
updated-dependencies:
- dependency-name: QRCoder
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 03:02:08 +00:00
dependabot[bot] 58794cbbb3 Bump actions/download-artifact from 7 to 8 (#696)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 22:55:48 +00:00
dependabot[bot] 81cf3f6b3d Bump actions/upload-artifact from 6 to 7 (#695)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-02 22:54:07 +00:00
Yaakov 4d56522e44 Merge pull request #692 from SteamRE/dependabot/nuget/DepotDownloader/Microsoft.Windows.CsWin32-0.3.269
Bump Microsoft.Windows.CsWin32 from 0.3.264 to 0.3.269
2026-01-20 11:24:16 +11:00
dependabot[bot] c220fc5b88 Bump Microsoft.Windows.CsWin32 from 0.3.264 to 0.3.269
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.269
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 21:04:49 +00:00
dependabot[bot] f5318b9ed6 Bump Microsoft.Windows.CsWin32 from 0.3.259 to 0.3.264 (#684)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.264
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 22:33:40 +00:00
dependabot[bot] 370b800967 Bump actions/upload-artifact from 5 to 6 (#685)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 22:33:00 +00:00
dependabot[bot] 027b4367fc Bump actions/download-artifact from 6 to 7 (#686)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 22:32:30 +00:00
dependabot[bot] 43a31b2713 Bump Microsoft.Windows.CsWin32 from 0.3.257 to 0.3.259 (#683)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.259
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-12 15:12:26 +00:00
dependabot[bot] 68089f6e7c Bump Microsoft.Windows.CsWin32 from 0.3.253 to 0.3.257 (#682)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.257
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 22:45:33 +00:00
dependabot[bot] e8e3d1bbfc Bump actions/checkout from 5 to 6 (#681)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 20:00:56 +00:00
dependabot[bot] f5c7f2d428 Bump Microsoft.Windows.CsWin32 from 0.3.248 to 0.3.253 (#680)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.253
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 19:58:54 +00:00
dependabot[bot] 5baa3f7625 Bump Microsoft.Windows.CsWin32 from 0.3.242 to 0.3.248 (#677)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.248
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 22:05:47 +00:00
Yaakov 8f8d0c9adf Merge pull request #675 from SteamRE/dependabot/nuget/DepotDownloader/Microsoft.Windows.CsWin32-0.3.242
Bump Microsoft.Windows.CsWin32 from 0.3.238 to 0.3.242
2025-11-11 10:29:50 +11:00
dependabot[bot] b8ca1d44f1 Bump Microsoft.Windows.CsWin32 from 0.3.238 to 0.3.242
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.242
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 19:13:05 +00:00
dependabot[bot] 007857c13f Bump Microsoft.Windows.CsWin32 from 0.3.228 to 0.3.238 (#672)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.238
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 23:25:05 +00:00
Nicholas Hastings 77f9067eaa Add basic support for downloading Workshop collections (#661) (#664)
* Add basic support for downloading Workshop collections (#661)

* Refactor published file processing logic

* Add messaging for unsupported Workshop file types
2025-11-02 15:50:36 +00:00
dependabot[bot] 9a2bafeb05 Bump actions/upload-artifact from 4 to 5 (#669)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 16:55:33 +00:00
dependabot[bot] 748fe11132 Bump Microsoft.Windows.CsWin32 from 0.3.217 to 0.3.228 (#667)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.228
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 16:55:14 +00:00
dependabot[bot] 97653ac481 Bump SteamKit2 from 3.3.0 to 3.3.1 (#668)
---
updated-dependencies:
- dependency-name: SteamKit2
  dependency-version: 3.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 16:54:57 +00:00
dependabot[bot] bfeeeb5b40 Bump actions/download-artifact from 4 to 6 (#666)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 16:54:29 +00:00
dependabot[bot] d5afc41b97 Bump protobuf-net from 3.2.52 to 3.2.56 (#648)
---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-version: 3.2.56
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 22:04:48 +00:00
dependabot[bot] 3d8b7dd696 Bump Microsoft.Windows.CsWin32 from 0.3.183 to 0.3.217 (#660)
---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-version: 0.3.217
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-20 21:46:09 +00:00
dependabot[bot] 560e48a8b5 Bump QRCoder from 1.6.0 to 1.7.0 (#657)
---
updated-dependencies:
- dependency-name: QRCoder
  dependency-version: 1.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-14 02:18:09 +00:00
Yaakov 54b0af377b Merge pull request #645 from SteamRE/dependabot/github_actions/actions/checkout-5 2025-09-09 07:57:23 +10:00
dependabot[bot] 5164a9f963 Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 21:36:29 +00:00
dependabot[bot] 2d2113a8e9 Bump actions/setup-dotnet from 4 to 5 (#650)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 21:30:00 +00:00
Pavel Djundik 5d6950afd9 Merge pull request #635 from SteamRE/dependabot/nuget/DepotDownloader/SteamKit2-3.3.0
Bump SteamKit2 from 3.2.0 to 3.3.0
2025-06-10 13:38:12 +03:00
Yaakov b042b055c4 Adapt to SteamRE/SteamKit#1544 so that we can build again 2025-06-10 12:18:47 +10:00
dependabot[bot] 02a525acdb Bump SteamKit2 from 3.2.0 to 3.3.0
---
updated-dependencies:
- dependency-name: SteamKit2
  dependency-version: 3.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 19:26:29 +00:00
Pavel Djundik c553ef4d60 Update steamkit, bump version 2025-05-09 22:38:25 +03:00
Pavel Djundik 9e203e0d7c Bump version to 3.3.0 2025-05-04 12:19:02 +03:00
Pavel Djundik 76038d83e8 Add a message about missing -remember-password 2025-05-04 12:18:46 +03:00
Pavel Djundik c21489d304 Remove asking for beta password interactively because it messes with beta branches
For example steamvr has a public beta branch, and it also includes shared redist depots which obviously have no beta branch.
2025-05-04 12:16:55 +03:00
Pavel Djundik 0d37091adf Do not attempt to get free license from anonymous account
Fixes #624
2025-05-04 11:53:34 +03:00
Pavel Djundik 0d66cf09ac Add -no-mobile 2025-04-24 18:00:40 +03:00
Pavel Djundik 8d875579c5 Print that -qr can be replaced with -username when password is remembered 2025-04-24 17:53:48 +03:00
Pavel Djundik ff9c709787 -qr should not be used with -account 2025-04-24 17:51:31 +03:00
Pavel Djundik bce88e4d32 Fix printing qr code 2025-04-24 17:47:34 +03:00
Pavel Djundik 08542bd09f Suggest logging in if no manifest code is returned 2025-04-24 17:08:19 +03:00
Pavel Djundik 665f83983b Implement private branches
Fixes #514
Fixes #620
2025-04-21 14:22:24 +03:00
Pavel Djundik 272f5b646a Ignore launch settings from git 2025-04-21 13:24:53 +03:00
Pavel Djundik f078581947 Print arguments that were not consumed
Fixes #88
2025-04-21 12:49:02 +03:00
Pavel Djundik 4896ac0788 Add token in winget action 2025-03-24 10:42:59 +02:00
Pavel Djundik 401d086191 Make stored credentials dictionaries case insensitive
Fixes #539
2025-03-24 10:29:40 +02:00
Pavel Djundik 8d8c6fc59a Merge pull request #610 from SteamRE/609-freetodownload-depotfromapp
Fix getting manifest code for freetodownload apps that use depotfromapp
2025-03-23 16:57:44 +02:00
Nicholas Hastings 19df5910c3 Fix getting manifest code for freetodownload apps that use depotfromapp 2025-03-22 15:29:54 -04:00
Pavel Djundik 1e72a47846 Add -debug to help 2025-03-12 11:42:56 +02:00
Pavel Djundik 2c07abb015 Exactly 64 is fine 2025-03-12 11:35:09 +02:00
Pavel Djundik 2682d44684 Add password length and non-ascii character warning
Fixes #601
2025-03-12 11:34:42 +02:00
Pavel Djundik be3682cd4b Remove -max-servers from help 2025-03-11 23:42:20 +02:00
Pavel Djundik 644e3f1ebc Add faq about -max-downloads 2025-03-11 23:42:05 +02:00
Pavel Djundik 56822a831f Return server when timing out as broken connection 2025-03-11 23:31:12 +02:00
Pavel Djundik 001f5303a7 Replace InvokeAsync with Parallel.ForEachAsync 2025-03-11 23:14:58 +02:00
Pavel Djundik 0150b7eff4 Vastly simplify CDNClientPool 2025-03-11 23:01:43 +02:00
Pavel Djundik 14c6a6dafa Remove -max-servers since it is unused 2025-03-11 22:41:44 +02:00
Yaakov 0617974ac0 Merge pull request #600 from SteamRE/dependabot/nuget/Microsoft.Windows.CsWin32-0.3.183
Bump Microsoft.Windows.CsWin32 from 0.3.162 to 0.3.183
2025-02-25 10:15:22 +11:00
dependabot[bot] 3249d284ba Bump Microsoft.Windows.CsWin32 from 0.3.162 to 0.3.183
Bumps [Microsoft.Windows.CsWin32](https://github.com/microsoft/CsWin32) from 0.3.162 to 0.3.183.
- [Release notes](https://github.com/microsoft/CsWin32/releases)
- [Commits](https://github.com/microsoft/CsWin32/compare/v0.3.162...v0.3.183)

---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 20:15:39 +00:00
Pavel Djundik 7b3cf4c6c7 Merge pull request #595 from SteamRE/dependabot/nuget/multi-acf9728d79
Bump protobuf-net and SteamKit2
2025-02-04 17:06:31 +02:00
dependabot[bot] b6fc6d5c6f Bump protobuf-net and SteamKit2
Bumps [protobuf-net](https://github.com/protobuf-net/protobuf-net) and [SteamKit2](https://github.com/SteamRE/SteamKit). These dependencies needed to be updated together.

Updates `protobuf-net` from 3.2.46 to 3.2.46
- [Release notes](https://github.com/protobuf-net/protobuf-net/releases)
- [Changelog](https://github.com/protobuf-net/protobuf-net/blob/main/docs/releasenotes.md)
- [Commits](https://github.com/protobuf-net/protobuf-net/commits)

Updates `SteamKit2` from 3.0.1 to 3.0.2
- [Release notes](https://github.com/SteamRE/SteamKit/releases)
- [Commits](https://github.com/SteamRE/SteamKit/compare/3.0.1...3.0.2)

---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: SteamKit2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 19:30:07 +00:00
Pavel Djundik 4dbe7ede14 Merge pull request #592 from SteamRE/dependabot/nuget/protobuf-net-3.2.46
Bump protobuf-net from 3.2.45 to 3.2.46
2025-01-29 15:24:01 +02:00
dependabot[bot] 24d7f0b02a Bump protobuf-net from 3.2.45 to 3.2.46
Bumps [protobuf-net](https://github.com/protobuf-net/protobuf-net) from 3.2.45 to 3.2.46.
- [Release notes](https://github.com/protobuf-net/protobuf-net/releases)
- [Changelog](https://github.com/protobuf-net/protobuf-net/blob/main/docs/releasenotes.md)
- [Commits](https://github.com/protobuf-net/protobuf-net/commits)

---
updated-dependencies:
- dependency-name: protobuf-net
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 19:30:49 +00:00
Yaakov 3bd2cf52ba Merge pull request #584 from SteamRE/dependabot/nuget/Microsoft.Windows.CsWin32-0.3.162
Bump Microsoft.Windows.CsWin32 from 0.3.106 to 0.3.162
2025-01-21 11:32:42 +11:00
dependabot[bot] f660b95d3f Bump Microsoft.Windows.CsWin32 from 0.3.106 to 0.3.162
Bumps [Microsoft.Windows.CsWin32](https://github.com/microsoft/CsWin32) from 0.3.106 to 0.3.162.
- [Release notes](https://github.com/microsoft/CsWin32/releases)
- [Commits](https://github.com/microsoft/CsWin32/compare/v0.3.106...v0.3.162)

---
updated-dependencies:
- dependency-name: Microsoft.Windows.CsWin32
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 19:34:23 +00:00
Pavel Djundik 17d3996588 Add -branchpassword, change -beta to -branch in help text 2025-01-04 13:03:53 +02:00
Pavel Djundik 5d0c4fb050 Bump version to 3.0.0 2025-01-04 12:39:39 +02:00
Pavel Djundik d087678628 Disallow using -remember-password and -qr without -username 2025-01-04 12:36:33 +02:00
Pavel Djundik deac83600c Merge pull request #565 from tpill90/AddLancacheSupport
Adding support for Lancache
2025-01-03 20:54:18 +02:00
Tim Pilius f567a63d84 Adding log message for when the lancache has been detected. 2025-01-03 13:28:42 -05:00
Tim Pilius e9a5abecb7 Adding support for Lancache with a new optional flag. 2025-01-03 12:20:47 -05:00
Pavel Djundik 5fe8a827d8 Use depotfromapp for getting manifest codes
Partially reverts 3143362d83

Fixes #569
Fixes #570
2024-11-27 19:58:00 +02:00
Pavel Djundik 682bfbac27 Do not abort downloading if one depot fails to get depot key 2024-11-27 19:44:50 +02:00
NicknineTheEagle 1c97b42124 Use Steam manifest format 2024-11-15 11:36:43 -07:00
Pavel Djundik 0fb803b5f6 Catch RequestFreeAppLicense exception 2024-11-14 11:08:17 +02:00
Pavel Djundik 2a939053d0 Add support for FreeToDownload apps 2024-11-14 11:08:17 +02:00
Pavel Djundik 4539666214 Cleanup 2024-11-14 10:55:43 +02:00
Yaakov fac3a22f4f Fix CA2022 warning 2024-11-14 10:53:26 +02:00
Yaakov 6c09739c8c Update to .NET 9 2024-11-14 10:53:26 +02:00
Pavel Djundik ff37997713 Add InvariantGlobalization 2024-11-14 10:52:04 +02:00
Pavel Djundik 72ffd79da6 Update packages 2024-11-11 11:46:18 +02:00
Ethan 2d06bf1c63 Improve -filelist comment
Fixes #554
2024-10-23 11:58:19 +03:00
Pavel Djundik 6a9364029b Tick callbacks in an infinite task 2024-10-21 19:27:03 +03:00
Pavel Djundik 9ed8a70508 Update steamkit 2024-10-21 14:53:41 +03:00
Pavel Djundik 247f8324ca Merge pull request #501 from giacomopc/master
-all-archs: Allow to download all archs
2024-10-18 13:31:23 +03:00
Pavel Djundik a5bdf23880 Use GetStdHandle_SafeHandle 2024-10-15 19:10:23 +03:00
Pavel Djundik 926c41e7e0 Use cswin32 in ansi detector 2024-10-14 21:44:06 +03:00
Pavel Djundik 33738aeb49 Disable progress ansi reporting on linux
Some terminals use this code to show notifications instead
2024-10-14 21:26:52 +03:00
Pavel Djundik 177c44ef4a Let steamkit handle connect timeouts
Ref: #546
2024-10-13 17:51:57 +03:00
Pavel Djundik 9f64bd0439 Await steam jobs instead of WaitUntilCallback 2024-10-13 17:51:53 +03:00
Pavel Djundik e7d637ace6 Change dependabot to weekly 2024-10-13 11:38:02 +03:00
Giacomo Preciado 39e43a1bf9 Merge branch 'master' into master 2024-09-16 13:46:11 -05:00
Giacomo Preciado b310d804f2 Updated README.md and help output 2024-09-16 13:41:44 -05:00
Giacomo Preciado ddda3924dc new parameter -all-archs downloads all OS archs 2024-05-28 23:56:04 -05:00
21 changed files with 894 additions and 651 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: daily
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: "github-actions"
+12 -14
View File
@@ -25,18 +25,16 @@ jobs:
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
uses: actions/setup-dotnet@v6
- name: Build
run: dotnet publish DepotDownloader/DepotDownloader.csproj -c ${{ matrix.configuration }} -o artifacts /p:ContinuousIntegrationBuild=true
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
with:
name: DepotDownloader-framework
@@ -72,7 +70,7 @@ jobs:
run: dotnet publish DepotDownloader/DepotDownloader.csproj --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime osx-arm64 --output selfcontained-osx-arm64
- name: Upload Windows-x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
with:
name: DepotDownloader-windows-x64
@@ -80,7 +78,7 @@ jobs:
if-no-files-found: error
- name: Upload Windows-arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
with:
name: DepotDownloader-windows-arm64
@@ -88,7 +86,7 @@ jobs:
if-no-files-found: error
- name: Upload Linux-x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
with:
name: DepotDownloader-linux-x64
@@ -96,7 +94,7 @@ jobs:
if-no-files-found: error
- name: Upload Linux-arm
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
with:
name: DepotDownloader-linux-arm
@@ -104,7 +102,7 @@ jobs:
if-no-files-found: error
- name: Upload Linux-arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
with:
name: DepotDownloader-linux-arm64
@@ -112,7 +110,7 @@ jobs:
if-no-files-found: error
- name: Upload macOS-x64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'macos-latest'
with:
name: DepotDownloader-macos-x64
@@ -120,7 +118,7 @@ jobs:
if-no-files-found: error
- name: Upload macOS-arm64
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release' && matrix.runs-on == 'macos-14'
with:
name: DepotDownloader-macos-arm64
@@ -133,7 +131,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
path: artifacts
@@ -164,7 +162,7 @@ jobs:
working-directory: release
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
draft: true
files: release/*
+3 -5
View File
@@ -17,12 +17,10 @@ jobs:
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Setup .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
uses: actions/setup-dotnet@v6
- name: Configure NuGet
run: |
@@ -33,7 +31,7 @@ jobs:
run: dotnet publish DepotDownloader/DepotDownloader.csproj -c ${{ matrix.configuration }} -o artifacts /p:ContinuousIntegrationBuild=true
- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
if: matrix.configuration == 'Release'
with:
name: DepotDownloader-${{ matrix.runs-on }}
+5 -1
View File
@@ -14,7 +14,11 @@ jobs:
run: |
$wingetPackage = "SteamRE.DepotDownloader"
$github = Invoke-RestMethod -uri "https://api.github.com/repos/SteamRE/DepotDownloader/releases"
$headers = @{
Authorization = "Bearer ${{ secrets.GITHUB_TOKEN }}"
}
$github = Invoke-RestMethod -uri "https://api.github.com/repos/SteamRE/DepotDownloader/releases" -Headers $headers
$targetRelease = $github | Where-Object -Property name -match '^DepotDownloader' | Select -First 1
$assets = $targetRelease | Select -ExpandProperty assets -First 1
+3 -2
View File
@@ -60,7 +60,7 @@ _ReSharper*
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
@@ -117,4 +117,5 @@ protobuf/
cryptopp/
# misc
Thumbs.db
Thumbs.db
launchSettings.json
+2 -2
View File
@@ -32,8 +32,8 @@ namespace DepotDownloader
AccountSettingsStore()
{
ContentServerPenalty = new ConcurrentDictionary<string, int>();
LoginTokens = [];
GuardData = [];
LoginTokens = new(StringComparer.OrdinalIgnoreCase);
GuardData = new(StringComparer.OrdinalIgnoreCase);
}
static bool Loaded
+5
View File
@@ -31,6 +31,11 @@ static class Ansi
return;
}
if (OperatingSystem.IsLinux())
{
return;
}
var (supportsAnsi, legacyConsole) = AnsiDetector.Detect(stdError: false, upgrade: true);
useProgress = supportsAnsi && !legacyConsole;
+34 -51
View File
@@ -7,6 +7,8 @@ using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Windows.Win32;
using Windows.Win32.System.Console;
namespace Spectre.Console;
@@ -45,7 +47,7 @@ internal static class AnsiDetector
return (true, false);
}
var supportsAnsi = Windows.SupportsAnsi(upgrade, stdError, out var legacyConsole);
var supportsAnsi = WindowsSupportsAnsi(upgrade, stdError, out var legacyConsole);
return (supportsAnsi, legacyConsole);
}
@@ -67,68 +69,49 @@ internal static class AnsiDetector
return (false, true);
}
private static class Windows
private static bool WindowsSupportsAnsi(bool upgrade, bool stdError, out bool isLegacy)
{
private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;
isLegacy = false;
[DllImport("kernel32.dll")]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
[DllImport("kernel32.dll")]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll")]
public static extern uint GetLastError();
public static bool SupportsAnsi(bool upgrade, bool stdError, out bool isLegacy)
try
{
isLegacy = false;
var @out = PInvoke.GetStdHandle_SafeHandle(stdError ? STD_HANDLE.STD_ERROR_HANDLE :STD_HANDLE.STD_OUTPUT_HANDLE);
try
if (!PInvoke.GetConsoleMode(@out, out var mode))
{
var @out = GetStdHandle(stdError ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE);
if (!GetConsoleMode(@out, out var mode))
{
// Could not get console mode, try TERM (set in cygwin, WSL-Shell).
var (ansiFromTerm, legacyFromTerm) = DetectFromTerm();
// Could not get console mode, try TERM (set in cygwin, WSL-Shell).
var (ansiFromTerm, legacyFromTerm) = DetectFromTerm();
isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy;
return ansiFromTerm;
isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy;
return ansiFromTerm;
}
if ((mode & CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0||true)
{
isLegacy = true;
if (!upgrade)
{
return false;
}
if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
// Try enable ANSI support.
mode |= CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING | CONSOLE_MODE.DISABLE_NEWLINE_AUTO_RETURN;
if (!PInvoke.SetConsoleMode(@out, mode))
{
isLegacy = true;
if (!upgrade)
{
return false;
}
// Try enable ANSI support.
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
if (!SetConsoleMode(@out, mode))
{
// Enabling failed.
return false;
}
isLegacy = false;
// Enabling failed.
return false;
}
return true;
}
catch
{
// All we know here is that we don't support ANSI.
return false;
isLegacy = false;
}
return true;
}
catch
{
// All we know here is that we don't support ANSI.
return false;
}
}
}
+39 -100
View File
@@ -2,10 +2,8 @@
// in file 'LICENSE', which is part of this source code package.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using SteamKit2.CDN;
@@ -16,139 +14,80 @@ namespace DepotDownloader
/// </summary>
class CDNClientPool
{
private const int ServerEndpointMinimumSize = 8;
private readonly Steam3Session steamSession;
private readonly uint appId;
public Client CDNClient { get; }
public Server ProxyServer { get; private set; }
private readonly ConcurrentStack<Server> activeConnectionPool = [];
private readonly BlockingCollection<Server> availableServerEndpoints = [];
private readonly AutoResetEvent populatePoolEvent = new(true);
private readonly Task monitorTask;
private readonly CancellationTokenSource shutdownToken = new();
public CancellationTokenSource ExhaustedToken { get; set; }
private readonly List<Server> servers = [];
private int nextServer;
public CDNClientPool(Steam3Session steamSession, uint appId)
{
this.steamSession = steamSession;
this.appId = appId;
CDNClient = new Client(steamSession.steamClient);
monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync).Unwrap();
}
public void Shutdown()
public async Task UpdateServerList()
{
shutdownToken.Cancel();
monitorTask.Wait();
}
var servers = await this.steamSession.steamContent.GetServersForSteamPipe();
private async Task<IReadOnlyCollection<Server>> FetchBootstrapServerListAsync()
{
try
{
var cdnServers = await this.steamSession.steamContent.GetServersForSteamPipe();
if (cdnServers != null)
ProxyServer = servers.Where(x => x.UseAsProxy).FirstOrDefault();
var weightedCdnServers = servers
.Where(server =>
{
return cdnServers;
var isEligibleForApp = server.AllowedAppIds.Length == 0 || server.AllowedAppIds.Contains(appId);
return isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN");
})
.Select(server =>
{
AccountSettingsStore.Instance.ContentServerPenalty.TryGetValue(server.Host, out var penalty);
return (server, penalty);
})
.OrderBy(pair => pair.penalty).ThenBy(pair => pair.server.WeightedLoad);
foreach (var (server, weight) in weightedCdnServers)
{
for (var i = 0; i < server.NumEntries; i++)
{
this.servers.Add(server);
}
}
catch (Exception ex)
if (this.servers.Count == 0)
{
Console.WriteLine("Failed to retrieve content server list: {0}", ex.Message);
}
return null;
}
private async Task ConnectionPoolMonitorAsync()
{
var didPopulate = false;
while (!shutdownToken.IsCancellationRequested)
{
populatePoolEvent.WaitOne(TimeSpan.FromSeconds(1));
// We want the Steam session so we can take the CellID from the session and pass it through to the ContentServer Directory Service
if (availableServerEndpoints.Count < ServerEndpointMinimumSize && steamSession.steamClient.IsConnected)
{
var servers = await FetchBootstrapServerListAsync().ConfigureAwait(false);
if (servers == null || servers.Count == 0)
{
ExhaustedToken?.Cancel();
return;
}
ProxyServer = servers.Where(x => x.UseAsProxy).FirstOrDefault();
var weightedCdnServers = servers
.Where(server =>
{
var isEligibleForApp = server.AllowedAppIds.Length == 0 || server.AllowedAppIds.Contains(appId);
return isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN");
})
.Select(server =>
{
AccountSettingsStore.Instance.ContentServerPenalty.TryGetValue(server.Host, out var penalty);
return (server, penalty);
})
.OrderBy(pair => pair.penalty).ThenBy(pair => pair.server.WeightedLoad);
foreach (var (server, weight) in weightedCdnServers)
{
for (var i = 0; i < server.NumEntries; i++)
{
availableServerEndpoints.Add(server);
}
}
didPopulate = true;
}
else if (availableServerEndpoints.Count == 0 && !steamSession.steamClient.IsConnected && didPopulate)
{
ExhaustedToken?.Cancel();
return;
}
throw new Exception("Failed to retrieve any download servers.");
}
}
private Server BuildConnection(CancellationToken token)
public Server GetConnection()
{
if (availableServerEndpoints.Count < ServerEndpointMinimumSize)
{
populatePoolEvent.Set();
}
return availableServerEndpoints.Take(token);
}
public Server GetConnection(CancellationToken token)
{
if (!activeConnectionPool.TryPop(out var connection))
{
connection = BuildConnection(token);
}
return connection;
return servers[nextServer % servers.Count];
}
public void ReturnConnection(Server server)
{
if (server == null) return;
activeConnectionPool.Push(server);
// nothing to do, maybe remove from ContentServerPenalty?
}
public void ReturnBrokenConnection(Server server)
{
if (server == null) return;
// Broken connections are not returned to the pool
lock (servers)
{
if (servers[nextServer % servers.Count] == server)
{
nextServer++;
// TODO: Add server to ContentServerPenalty
}
}
}
}
}
+76
View File
@@ -0,0 +1,76 @@
// This file is subject to the terms and conditions defined
// in file 'LICENSE', which is part of this source code package.
using System;
using System.Threading.Tasks;
using SteamKit2.Authentication;
namespace DepotDownloader
{
// This is practically copied from https://github.com/SteamRE/SteamKit/blob/master/SteamKit2/SteamKit2/Steam/Authentication/UserConsoleAuthenticator.cs
internal class ConsoleAuthenticator : IAuthenticator
{
/// <inheritdoc />
public Task<string> GetDeviceCodeAsync(bool previousCodeWasIncorrect)
{
if (previousCodeWasIncorrect)
{
Console.Error.WriteLine("The previous 2-factor auth code you have provided is incorrect.");
}
string code;
do
{
Console.Error.Write("STEAM GUARD! Please enter your 2-factor auth code from your authenticator app: ");
code = Console.ReadLine()?.Trim();
if (code == null)
{
break;
}
}
while (string.IsNullOrEmpty(code));
return Task.FromResult(code!);
}
/// <inheritdoc />
public Task<string> GetEmailCodeAsync(string email, bool previousCodeWasIncorrect)
{
if (previousCodeWasIncorrect)
{
Console.Error.WriteLine("The previous 2-factor auth code you have provided is incorrect.");
}
string code;
do
{
Console.Error.Write($"STEAM GUARD! Please enter the auth code sent to the email at {email}: ");
code = Console.ReadLine()?.Trim();
if (code == null)
{
break;
}
}
while (string.IsNullOrEmpty(code));
return Task.FromResult(code!);
}
/// <inheritdoc />
public Task<bool> AcceptDeviceConfirmationAsync()
{
if (ContentDownloader.Config.SkipAppConfirmation)
{
return Task.FromResult(false);
}
Console.Error.WriteLine("STEAM GUARD! Use the Steam Mobile App to confirm your sign in...");
return Task.FromResult(true);
}
}
}
+242 -212
View File
@@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -35,6 +36,16 @@ namespace DepotDownloader
private const string CONFIG_DIR = ".DepotDownloader";
private static readonly string STAGING_DIR = Path.Combine(CONFIG_DIR, "staging");
private static readonly FrozenSet<EWorkshopFileType> SupportedWorkshopFileTypes = FrozenSet.ToFrozenSet(new[]
{
EWorkshopFileType.Community,
EWorkshopFileType.Art,
EWorkshopFileType.Screenshot,
EWorkshopFileType.Merch,
EWorkshopFileType.IntegratedGuide,
EWorkshopFileType.ControllerBinding,
});
private sealed class DepotDownloadInfo(
uint depotid, uint appId, ulong manifestId, string branch,
string installDir, byte[] depotKey)
@@ -106,7 +117,7 @@ namespace DepotDownloader
return false;
}
static bool AccountHasAccess(uint depotId)
static async Task<bool> AccountHasAccess(uint appId, uint depotId)
{
if (steam3 == null || steam3.steamUser.SteamID == null || (steam3.Licenses == null && steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser))
return false;
@@ -121,7 +132,7 @@ namespace DepotDownloader
licenseQuery = steam3.Licenses.Select(x => x.PackageID).Distinct();
}
steam3.RequestPackageInfo(licenseQuery);
await steam3.RequestPackageInfo(licenseQuery);
foreach (var license in licenseQuery)
{
@@ -135,6 +146,11 @@ namespace DepotDownloader
}
}
// Check if this app is free to download without a license
var info = GetSteam3AppSection(appId, EAppInfoSection.Common);
if (info != null && info["FreeToDownload"].AsBoolean())
return true;
return false;
}
@@ -184,7 +200,21 @@ namespace DepotDownloader
return uint.Parse(buildid.Value);
}
static ulong GetSteam3DepotManifest(uint depotId, uint appId, string branch)
static uint GetSteam3DepotProxyAppId(uint depotId, uint appId)
{
var depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
var depotChild = depots[depotId.ToString()];
if (depotChild == KeyValue.Invalid)
return INVALID_APP_ID;
if (depotChild["depotfromapp"] == KeyValue.Invalid)
return INVALID_APP_ID;
return depotChild["depotfromapp"].AsUnsignedInteger();
}
static async Task<ulong> GetSteam3DepotManifest(uint depotId, uint appId, string branch)
{
var depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
var depotChild = depots[depotId.ToString()];
@@ -206,66 +236,62 @@ namespace DepotDownloader
return INVALID_MANIFEST_ID;
}
steam3.RequestAppInfo(otherAppId);
await steam3.RequestAppInfo(otherAppId);
return GetSteam3DepotManifest(depotId, otherAppId, branch);
return await GetSteam3DepotManifest(depotId, otherAppId, branch);
}
var manifests = depotChild["manifests"];
var manifests_encrypted = depotChild["encryptedmanifests"];
if (manifests.Children.Count == 0 && manifests_encrypted.Children.Count == 0)
if (manifests.Children.Count == 0)
return INVALID_MANIFEST_ID;
var node = manifests[branch]["gid"];
if (node == KeyValue.Invalid && !string.Equals(branch, DEFAULT_BRANCH, StringComparison.OrdinalIgnoreCase))
// Non passworded branch, found the manifest
if (node.Value != null)
return ulong.Parse(node.Value);
// If we requested public branch and it had no manifest, nothing to do
if (string.Equals(branch, DEFAULT_BRANCH, StringComparison.OrdinalIgnoreCase))
return INVALID_MANIFEST_ID;
// Either the branch just doesn't exist, or it has a password
if (string.IsNullOrEmpty(Config.BetaPassword))
{
var node_encrypted = manifests_encrypted[branch];
if (node_encrypted != KeyValue.Invalid)
{
var password = Config.BetaPassword;
while (string.IsNullOrEmpty(password))
{
Console.Write("Please enter the password for branch {0}: ", branch);
Config.BetaPassword = password = Console.ReadLine();
}
var encrypted_gid = node_encrypted["gid"];
if (encrypted_gid != KeyValue.Invalid)
{
// Submit the password to Steam now to get encryption keys
steam3.CheckAppBetaPassword(appId, Config.BetaPassword);
if (!steam3.AppBetaPasswords.TryGetValue(branch, out var appBetaPassword))
{
Console.WriteLine("Password was invalid for branch {0}", branch);
return INVALID_MANIFEST_ID;
}
var input = Util.DecodeHexString(encrypted_gid.Value);
byte[] manifest_bytes;
try
{
manifest_bytes = Util.SymmetricDecryptECB(input, appBetaPassword);
}
catch (Exception e)
{
Console.WriteLine("Failed to decrypt branch {0}: {1}", branch, e.Message);
return INVALID_MANIFEST_ID;
}
return BitConverter.ToUInt64(manifest_bytes, 0);
}
Console.WriteLine("Unhandled depot encryption for depotId {0}", depotId);
return INVALID_MANIFEST_ID;
}
Console.WriteLine($"Branch {branch} for depot {depotId} was not found, either it does not exist or it has a password.");
return INVALID_MANIFEST_ID;
}
if (!steam3.AppBetaPasswords.ContainsKey(branch))
{
// Submit the password to Steam now to get encryption keys
await steam3.CheckAppBetaPassword(appId, Config.BetaPassword);
if (!steam3.AppBetaPasswords.ContainsKey(branch))
{
Console.WriteLine($"Error: Password was invalid for branch {branch} (or the branch does not exist)");
return INVALID_MANIFEST_ID;
}
}
// Got the password, request private depot section
// TODO: We're probably repeating this request for every depot?
var privateDepotSection = await steam3.GetPrivateBetaDepotSection(appId, branch);
// Now repeat the same code to get the manifest gid from depot section
depotChild = privateDepotSection[depotId.ToString()];
if (depotChild == KeyValue.Invalid)
return INVALID_MANIFEST_ID;
manifests = depotChild["manifests"];
if (manifests.Children.Count == 0)
return INVALID_MANIFEST_ID;
node = manifests[branch]["gid"];
if (node.Value == null)
return INVALID_MANIFEST_ID;
@@ -307,38 +333,67 @@ namespace DepotDownloader
return false;
}
Task.Run(steam3.TickCallbacks);
return true;
}
public static void ShutdownSteam3()
{
if (cdnPool != null)
{
cdnPool.Shutdown();
cdnPool = null;
}
if (steam3 == null)
return;
steam3.Disconnect();
}
public static async Task DownloadPubfileAsync(uint appId, ulong publishedFileId)
private static async Task ProcessPublishedFileAsync(uint appId, ulong publishedFileId, List<ValueTuple<string, string>> fileUrls, List<ulong> contentFileIds)
{
var details = steam3.GetPublishedFileDetails(appId, publishedFileId);
var details = await steam3.GetPublishedFileDetails(appId, publishedFileId);
var fileType = (EWorkshopFileType)details.file_type;
if (!string.IsNullOrEmpty(details?.file_url))
if (fileType == EWorkshopFileType.Collection)
{
await DownloadWebFile(appId, details.filename, details.file_url);
foreach (var child in details.children)
{
await ProcessPublishedFileAsync(appId, child.publishedfileid, fileUrls, contentFileIds);
}
}
else if (details?.hcontent_file > 0)
else if (SupportedWorkshopFileTypes.Contains(fileType))
{
await DownloadAppAsync(appId, new List<(uint, ulong)> { (appId, details.hcontent_file) }, DEFAULT_BRANCH, null, null, null, false, true);
if (!string.IsNullOrEmpty(details?.file_url))
{
fileUrls.Add((details.filename, details.file_url));
}
else if (details?.hcontent_file > 0)
{
contentFileIds.Add(details.hcontent_file);
}
else
{
Console.WriteLine("Unable to locate manifest ID for published file {0}", publishedFileId);
}
}
else
{
Console.WriteLine("Unable to locate manifest ID for published file {0}", publishedFileId);
Console.WriteLine("Published file {0} has unsupported file type {1}. Skipping file", publishedFileId, fileType);
}
}
public static async Task DownloadPubfileAsync(uint appId, ulong publishedFileId)
{
List<ValueTuple<string, string>> fileUrls = new();
List<ulong> contentFileIds = new();
await ProcessPublishedFileAsync(appId, publishedFileId, fileUrls, contentFileIds);
foreach (var item in fileUrls)
{
await DownloadWebFile(appId, item.Item1, item.Item2);
}
if (contentFileIds.Count > 0)
{
var depotManifestIds = contentFileIds.Select(id => (appId, id)).ToList();
await DownloadAppAsync(appId, depotManifestIds, DEFAULT_BRANCH, null, null, null, false, true);
}
}
@@ -348,7 +403,7 @@ namespace DepotDownloader
if (steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser)
{
details = steam3.GetUGCDetails(ugcId);
details = await steam3.GetUGCDetails(ugcId);
}
else
{
@@ -361,7 +416,7 @@ namespace DepotDownloader
}
else
{
await DownloadAppAsync(appId, new List<(uint, ulong)> { (appId, ugcId) }, DEFAULT_BRANCH, null, null, null, false, true);
await DownloadAppAsync(appId, [(appId, ugcId)], DEFAULT_BRANCH, null, null, null, false, true);
}
}
@@ -410,16 +465,16 @@ namespace DepotDownloader
Directory.CreateDirectory(Path.Combine(configPath, CONFIG_DIR));
DepotConfigStore.LoadFromFile(Path.Combine(configPath, CONFIG_DIR, "depot.config"));
steam3?.RequestAppInfo(appId);
await steam3?.RequestAppInfo(appId);
if (!AccountHasAccess(appId))
if (!await AccountHasAccess(appId, appId))
{
if (steam3.RequestFreeAppLicense(appId))
if (steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser && await steam3.RequestFreeAppLicense(appId))
{
Console.WriteLine("Obtained FreeOnDemand license for app {0}", appId);
// Fetch app info again in case we didn't get it fully without a license.
steam3.RequestAppInfo(appId, true);
await steam3.RequestAppInfo(appId, true);
}
else
{
@@ -476,7 +531,8 @@ namespace DepotDownloader
continue;
}
if (depotConfig["osarch"] != KeyValue.Invalid &&
if (!Config.DownloadAllArchs &&
depotConfig["osarch"] != KeyValue.Invalid &&
!string.IsNullOrWhiteSpace(depotConfig["osarch"].Value))
{
var depotArch = depotConfig["osarch"].Value;
@@ -523,13 +579,15 @@ namespace DepotDownloader
foreach (var (depotId, manifestId) in depotManifestIds)
{
var info = GetDepotInfo(depotId, appId, manifestId, branch);
var info = await GetDepotInfo(depotId, appId, manifestId, branch);
if (info != null)
{
infos.Add(info);
}
}
Console.WriteLine();
try
{
await DownloadSteam3Async(infos).ConfigureAwait(false);
@@ -541,12 +599,14 @@ namespace DepotDownloader
}
}
static DepotDownloadInfo GetDepotInfo(uint depotId, uint appId, ulong manifestId, string branch)
static async Task<DepotDownloadInfo> GetDepotInfo(uint depotId, uint appId, ulong manifestId, string branch)
{
if (steam3 != null && appId != INVALID_APP_ID)
steam3.RequestAppInfo(appId);
{
await steam3.RequestAppInfo(appId);
}
if (!AccountHasAccess(depotId))
if (!await AccountHasAccess(appId, depotId))
{
Console.WriteLine("Depot {0} is not available from this account.", depotId);
@@ -555,12 +615,12 @@ namespace DepotDownloader
if (manifestId == INVALID_MANIFEST_ID)
{
manifestId = GetSteam3DepotManifest(depotId, appId, branch);
manifestId = await GetSteam3DepotManifest(depotId, appId, branch);
if (manifestId == INVALID_MANIFEST_ID && !string.Equals(branch, DEFAULT_BRANCH, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Warning: Depot {0} does not have branch named \"{1}\". Trying {2} branch.", depotId, branch, DEFAULT_BRANCH);
branch = DEFAULT_BRANCH;
manifestId = GetSteam3DepotManifest(depotId, appId, branch);
manifestId = await GetSteam3DepotManifest(depotId, appId, branch);
}
if (manifestId == INVALID_MANIFEST_ID)
@@ -570,7 +630,7 @@ namespace DepotDownloader
}
}
steam3.RequestDepotKey(depotId, appId);
await steam3.RequestDepotKey(depotId, appId);
if (!steam3.DepotKeys.TryGetValue(depotId, out var depotKey))
{
Console.WriteLine("No valid depot key for {0}, unable to download.", depotId);
@@ -585,13 +645,25 @@ namespace DepotDownloader
return null;
}
return new DepotDownloadInfo(depotId, appId, manifestId, branch, installDir, depotKey);
// For depots that are proxied through depotfromapp, we still need to resolve the proxy app id, unless the app is freetodownload
var containingAppId = appId;
var proxyAppId = GetSteam3DepotProxyAppId(depotId, appId);
if (proxyAppId != INVALID_APP_ID)
{
var common = GetSteam3AppSection(appId, EAppInfoSection.Common);
if (common == null || !common["FreeToDownload"].AsBoolean())
{
containingAppId = proxyAppId;
}
}
return new DepotDownloadInfo(depotId, containingAppId, manifestId, branch, installDir, depotKey);
}
private class ChunkMatch(ProtoManifest.ChunkData oldChunk, ProtoManifest.ChunkData newChunk)
private class ChunkMatch(DepotManifest.ChunkData oldChunk, DepotManifest.ChunkData newChunk)
{
public ProtoManifest.ChunkData OldChunk { get; } = oldChunk;
public ProtoManifest.ChunkData NewChunk { get; } = newChunk;
public DepotManifest.ChunkData OldChunk { get; } = oldChunk;
public DepotManifest.ChunkData NewChunk { get; } = newChunk;
}
private class DepotFilesData
@@ -599,9 +671,9 @@ namespace DepotDownloader
public DepotDownloadInfo depotDownloadInfo;
public DepotDownloadCounter depotCounter;
public string stagingDir;
public ProtoManifest manifest;
public ProtoManifest previousManifest;
public List<ProtoManifest.FileData> filteredFiles;
public DepotManifest manifest;
public DepotManifest previousManifest;
public List<DepotManifest.FileData> filteredFiles;
public HashSet<string> allFileNames;
}
@@ -631,9 +703,9 @@ namespace DepotDownloader
{
Ansi.Progress(Ansi.ProgressState.Indeterminate);
var cts = new CancellationTokenSource();
cdnPool.ExhaustedToken = cts;
await cdnPool.UpdateServerList();
var cts = new CancellationTokenSource();
var downloadCounter = new GlobalDownloadCounter();
var depotsToDownload = new List<DepotFilesData>(depots.Count);
var allFileNamesAllDepots = new HashSet<string>();
@@ -684,8 +756,8 @@ namespace DepotDownloader
Console.WriteLine("Processing depot {0}", depot.DepotId);
ProtoManifest oldProtoManifest = null;
ProtoManifest newProtoManifest = null;
DepotManifest oldManifest = null;
DepotManifest newManifest = null;
var configDir = Path.Combine(depot.InstallDir, CONFIG_DIR);
var lastManifestId = INVALID_MANIFEST_ID;
@@ -697,72 +769,28 @@ namespace DepotDownloader
if (lastManifestId != INVALID_MANIFEST_ID)
{
var oldManifestFileName = Path.Combine(configDir, string.Format("{0}_{1}.bin", depot.DepotId, lastManifestId));
if (File.Exists(oldManifestFileName))
{
byte[] expectedChecksum;
try
{
expectedChecksum = File.ReadAllBytes(oldManifestFileName + ".sha");
}
catch (IOException)
{
expectedChecksum = null;
}
oldProtoManifest = ProtoManifest.LoadFromFile(oldManifestFileName, out var currentChecksum);
if (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum))
{
// We only have to show this warning if the old manifest ID was different
if (lastManifestId != depot.ManifestId)
Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", lastManifestId);
oldProtoManifest = null;
}
}
// We only have to show this warning if the old manifest ID was different
var badHashWarning = (lastManifestId != depot.ManifestId);
oldManifest = Util.LoadManifestFromFile(configDir, depot.DepotId, lastManifestId, badHashWarning);
}
if (lastManifestId == depot.ManifestId && oldProtoManifest != null)
if (lastManifestId == depot.ManifestId && oldManifest != null)
{
newProtoManifest = oldProtoManifest;
newManifest = oldManifest;
Console.WriteLine("Already have manifest {0} for depot {1}.", depot.ManifestId, depot.DepotId);
}
else
{
var newManifestFileName = Path.Combine(configDir, string.Format("{0}_{1}.bin", depot.DepotId, depot.ManifestId));
if (newManifestFileName != null)
{
byte[] expectedChecksum;
newManifest = Util.LoadManifestFromFile(configDir, depot.DepotId, depot.ManifestId, true);
try
{
expectedChecksum = File.ReadAllBytes(newManifestFileName + ".sha");
}
catch (IOException)
{
expectedChecksum = null;
}
newProtoManifest = ProtoManifest.LoadFromFile(newManifestFileName, out var currentChecksum);
if (newProtoManifest != null && (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum)))
{
Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", depot.ManifestId);
newProtoManifest = null;
}
}
if (newProtoManifest != null)
if (newManifest != null)
{
Console.WriteLine("Already have manifest {0} for depot {1}.", depot.ManifestId, depot.DepotId);
}
else
{
Console.Write("Downloading depot manifest... ");
Console.WriteLine($"Downloading depot {depot.DepotId} manifest");
DepotManifest depotManifest = null;
ulong manifestRequestCode = 0;
var manifestRequestCodeExpiration = DateTime.MinValue;
@@ -774,7 +802,7 @@ namespace DepotDownloader
try
{
connection = cdnPool.GetConnection(cts.Token);
connection = cdnPool.GetConnection();
string cdnToken = null;
if (steam3.CDNAuthTokens.TryGetValue((depot.DepotId, connection.Host), out var authTokenCallbackPromise))
@@ -800,7 +828,6 @@ namespace DepotDownloader
// If we could not get the manifest code, this is a fatal error
if (manifestRequestCode == 0)
{
Console.WriteLine("No manifest request code was returned for {0} {1}", depot.DepotId, depot.ManifestId);
cts.Cancel();
}
}
@@ -810,7 +837,7 @@ namespace DepotDownloader
depot.ManifestId,
connection,
cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy");
depotManifest = await cdnPool.CDNClient.DownloadManifestAsync(
newManifest = await cdnPool.CDNClient.DownloadManifestAsync(
depot.DepotId,
depot.ManifestId,
manifestRequestCode,
@@ -862,9 +889,9 @@ namespace DepotDownloader
cdnPool.ReturnBrokenConnection(connection);
Console.WriteLine("Encountered error downloading manifest for depot {0} {1}: {2}", depot.DepotId, depot.ManifestId, e.Message);
}
} while (depotManifest == null);
} while (newManifest == null);
if (depotManifest == null)
if (newManifest == null)
{
Console.WriteLine("\nUnable to download manifest {0} for depot {1}", depot.ManifestId, depot.DepotId);
cts.Cancel();
@@ -873,28 +900,21 @@ namespace DepotDownloader
// Throw the cancellation exception if requested so that this task is marked failed
cts.Token.ThrowIfCancellationRequested();
newProtoManifest = new ProtoManifest(depotManifest, depot.ManifestId);
newProtoManifest.SaveToFile(newManifestFileName, out var checksum);
File.WriteAllBytes(newManifestFileName + ".sha", checksum);
Console.WriteLine(" Done!");
Util.SaveManifestToFile(configDir, newManifest);
}
}
newProtoManifest.Files.Sort((x, y) => string.Compare(x.FileName, y.FileName, StringComparison.Ordinal));
Console.WriteLine("Manifest {0} ({1})", depot.ManifestId, newProtoManifest.CreationTime);
Console.WriteLine("Manifest {0} ({1})", depot.ManifestId, newManifest.CreationTime);
if (Config.DownloadManifestOnly)
{
DumpManifestToTextFile(depot, newProtoManifest);
DumpManifestToTextFile(depot, newManifest);
return null;
}
var stagingDir = Path.Combine(depot.InstallDir, STAGING_DIR);
var filesAfterExclusions = newProtoManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).ToList();
var filesAfterExclusions = newManifest.Files.AsParallel().Where(f => TestIsFileIncluded(f.FileName)).ToList();
var allFileNames = new HashSet<string>(filesAfterExclusions.Count);
// Pre-process
@@ -926,8 +946,8 @@ namespace DepotDownloader
depotDownloadInfo = depot,
depotCounter = depotCounter,
stagingDir = stagingDir,
manifest = newProtoManifest,
previousManifest = oldProtoManifest,
manifest = newManifest,
previousManifest = oldManifest,
filteredFiles = filesAfterExclusions,
allFileNames = allFileNames
};
@@ -942,20 +962,27 @@ namespace DepotDownloader
Console.WriteLine("Downloading depot {0}", depot.DepotId);
var files = depotFilesData.filteredFiles.Where(f => !f.Flags.HasFlag(EDepotFileFlag.Directory)).ToArray();
var networkChunkQueue = new ConcurrentQueue<(FileStreamData fileStreamData, ProtoManifest.FileData fileData, ProtoManifest.ChunkData chunk)>();
var networkChunkQueue = new ConcurrentQueue<(FileStreamData fileStreamData, DepotManifest.FileData fileData, DepotManifest.ChunkData chunk)>();
await Util.InvokeAsync(
files.Select(file => new Func<Task>(async () =>
await Task.Run(() => DownloadSteam3AsyncDepotFile(cts, downloadCounter, depotFilesData, file, networkChunkQueue)))),
maxDegreeOfParallelism: Config.MaxDownloads
);
var parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = Config.MaxDownloads,
CancellationToken = cts.Token
};
await Util.InvokeAsync(
networkChunkQueue.Select(q => new Func<Task>(async () =>
await Task.Run(() => DownloadSteam3AsyncDepotFileChunk(cts, downloadCounter, depotFilesData,
q.fileData, q.fileStreamData, q.chunk)))),
maxDegreeOfParallelism: Config.MaxDownloads
);
await Parallel.ForEachAsync(files, parallelOptions, async (file, cancellationToken) =>
{
await Task.Yield();
DownloadSteam3AsyncDepotFile(cts, downloadCounter, depotFilesData, file, networkChunkQueue);
});
await Parallel.ForEachAsync(networkChunkQueue, parallelOptions, async (q, cancellationToken) =>
{
await DownloadSteam3AsyncDepotFileChunk(
cts, downloadCounter, depotFilesData,
q.fileData, q.fileStreamData, q.chunk
);
});
// Check for deleted files if updating the depot.
if (depotFilesData.previousManifest != null)
@@ -996,8 +1023,8 @@ namespace DepotDownloader
CancellationTokenSource cts,
GlobalDownloadCounter downloadCounter,
DepotFilesData depotFilesData,
ProtoManifest.FileData file,
ConcurrentQueue<(FileStreamData, ProtoManifest.FileData, ProtoManifest.ChunkData)> networkChunkQueue)
DepotManifest.FileData file,
ConcurrentQueue<(FileStreamData, DepotManifest.FileData, DepotManifest.ChunkData)> networkChunkQueue)
{
cts.Token.ThrowIfCancellationRequested();
@@ -1005,7 +1032,7 @@ namespace DepotDownloader
var stagingDir = depotFilesData.stagingDir;
var depotDownloadCounter = depotFilesData.depotCounter;
var oldProtoManifest = depotFilesData.previousManifest;
ProtoManifest.FileData oldManifestFile = null;
DepotManifest.FileData oldManifestFile = null;
if (oldProtoManifest != null)
{
oldManifestFile = oldProtoManifest.Files.SingleOrDefault(f => f.FileName == file.FileName);
@@ -1020,7 +1047,7 @@ namespace DepotDownloader
File.Delete(fileStagingPath);
}
List<ProtoManifest.ChunkData> neededChunks;
List<DepotManifest.ChunkData> neededChunks;
var fi = new FileInfo(fileFinalPath);
var fileDidExist = fi.Exists;
if (!fileDidExist)
@@ -1038,7 +1065,7 @@ namespace DepotDownloader
throw new ContentDownloaderException(string.Format("Failed to allocate file {0}: {1}", fileFinalPath, ex.Message));
}
neededChunks = new List<ProtoManifest.ChunkData>(file.Chunks);
neededChunks = new List<DepotManifest.ChunkData>(file.Chunks);
}
else
{
@@ -1082,7 +1109,7 @@ namespace DepotDownloader
fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin);
var adler = Util.AdlerHash(fsOld, (int)match.OldChunk.UncompressedLength);
if (!adler.SequenceEqual(match.OldChunk.Checksum))
if (!adler.SequenceEqual(BitConverter.GetBytes(match.OldChunk.Checksum)))
{
neededChunks.Add(match.NewChunk);
}
@@ -1114,7 +1141,7 @@ namespace DepotDownloader
fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin);
var tmp = new byte[match.OldChunk.UncompressedLength];
fsOld.Read(tmp, 0, tmp.Length);
fsOld.ReadExactly(tmp);
fs.Seek((long)match.NewChunk.Offset, SeekOrigin.Begin);
fs.Write(tmp, 0, tmp.Length);
@@ -1201,9 +1228,9 @@ namespace DepotDownloader
CancellationTokenSource cts,
GlobalDownloadCounter downloadCounter,
DepotFilesData depotFilesData,
ProtoManifest.FileData file,
DepotManifest.FileData file,
FileStreamData fileStreamData,
ProtoManifest.ChunkData chunk)
DepotManifest.ChunkData chunk)
{
cts.Token.ThrowIfCancellationRequested();
@@ -1212,17 +1239,8 @@ namespace DepotDownloader
var chunkID = Convert.ToHexString(chunk.ChunkID).ToLowerInvariant();
var data = new DepotManifest.ChunkData
{
ChunkID = chunk.ChunkID,
Checksum = BitConverter.ToUInt32(chunk.Checksum),
Offset = chunk.Offset,
CompressedLength = chunk.CompressedLength,
UncompressedLength = chunk.UncompressedLength
};
var written = 0;
var chunkBuffer = ArrayPool<byte>.Shared.Rent((int)data.UncompressedLength);
var chunkBuffer = ArrayPool<byte>.Shared.Rent((int)chunk.UncompressedLength);
try
{
@@ -1234,7 +1252,7 @@ namespace DepotDownloader
try
{
connection = cdnPool.GetConnection(cts.Token);
connection = cdnPool.GetConnection();
string cdnToken = null;
if (steam3.CDNAuthTokens.TryGetValue((depot.DepotId, connection.Host), out var authTokenCallbackPromise))
@@ -1246,7 +1264,7 @@ namespace DepotDownloader
DebugLog.WriteLine("ContentDownloader", "Downloading chunk {0} from {1} with {2}", chunkID, connection, cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy");
written = await cdnPool.CDNClient.DownloadDepotChunkAsync(
depot.DepotId,
data,
chunk,
connection,
chunkBuffer,
depot.DepotKey,
@@ -1260,6 +1278,7 @@ namespace DepotDownloader
catch (TaskCanceledException)
{
Console.WriteLine("Connection timeout downloading chunk {0}", chunkID);
cdnPool.ReturnBrokenConnection(connection);
}
catch (SteamKitWebRequestException e)
{
@@ -1315,7 +1334,7 @@ namespace DepotDownloader
fileStreamData.fileStream = File.Open(fileFinalPath, FileMode.Open);
}
fileStreamData.fileStream.Seek((long)data.Offset, SeekOrigin.Begin);
fileStreamData.fileStream.Seek((long)chunk.Offset, SeekOrigin.Begin);
await fileStreamData.fileStream.WriteAsync(chunkBuffer.AsMemory(0, written), cts.Token);
}
finally
@@ -1359,44 +1378,55 @@ namespace DepotDownloader
}
}
static void DumpManifestToTextFile(DepotDownloadInfo depot, ProtoManifest manifest)
class ChunkIdComparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[] x, byte[] y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.SequenceEqual(y);
}
public int GetHashCode(byte[] obj)
{
ArgumentNullException.ThrowIfNull(obj);
// ChunkID is SHA-1, so we can just use the first 4 bytes
return BitConverter.ToInt32(obj, 0);
}
}
static void DumpManifestToTextFile(DepotDownloadInfo depot, DepotManifest manifest)
{
var txtManifest = Path.Combine(depot.InstallDir, $"manifest_{depot.DepotId}_{depot.ManifestId}.txt");
using var sw = new StreamWriter(txtManifest);
sw.WriteLine($"Content Manifest for Depot {depot.DepotId}");
sw.WriteLine($"Content Manifest for Depot {depot.DepotId} ");
sw.WriteLine();
sw.WriteLine($"Manifest ID / date : {depot.ManifestId} / {manifest.CreationTime}");
sw.WriteLine($"Manifest ID / date : {depot.ManifestId} / {manifest.CreationTime} ");
int numFiles = 0, numChunks = 0;
ulong uncompressedSize = 0, compressedSize = 0;
var uniqueChunks = new HashSet<byte[]>(new ChunkIdComparer());
foreach (var file in manifest.Files)
{
if (file.Flags.HasFlag(EDepotFileFlag.Directory))
continue;
numFiles++;
numChunks += file.Chunks.Count;
foreach (var chunk in file.Chunks)
{
uncompressedSize += chunk.UncompressedLength;
compressedSize += chunk.CompressedLength;
uniqueChunks.Add(chunk.ChunkID);
}
}
sw.WriteLine($"Total number of files : {numFiles}");
sw.WriteLine($"Total number of chunks : {numChunks}");
sw.WriteLine($"Total bytes on disk : {uncompressedSize}");
sw.WriteLine($"Total bytes compressed : {compressedSize}");
sw.WriteLine($"Total number of files : {manifest.Files.Count} ");
sw.WriteLine($"Total number of chunks : {uniqueChunks.Count} ");
sw.WriteLine($"Total bytes on disk : {manifest.TotalUncompressedSize} ");
sw.WriteLine($"Total bytes compressed : {manifest.TotalCompressedSize} ");
sw.WriteLine();
sw.WriteLine();
sw.WriteLine(" Size Chunks File SHA Flags Name");
foreach (var file in manifest.Files)
{
var sha1Hash = BitConverter.ToString(file.FileHash).Replace("-", "");
sw.WriteLine($"{file.TotalSize,14} {file.Chunks.Count,6} {sha1Hash} {file.Flags,5:D} {file.FileName}");
var sha1Hash = Convert.ToHexString(file.FileHash).ToLower();
sw.WriteLine($"{file.TotalSize,14:d} {file.Chunks.Count,6:d} {sha1Hash} {(int)file.Flags,5:x} {file.FileName}");
}
}
}
+9 -8
View File
@@ -1,16 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<RollForward>LatestMajor</RollForward>
<Version>2.7.3</Version>
<Version>3.4.0</Version>
<Description>Steam Downloading Utility</Description>
<Authors>SteamRE Team</Authors>
<Copyright>Copyright © SteamRE Team 2024</Copyright>
<Copyright>Copyright © SteamRE Team 2025</Copyright>
<ApplicationIcon>..\Icon\DepotDownloader.ico</ApplicationIcon>
<Deterministic>true</Deterministic>
<TreatWarningsAsErrors Condition="'$(ContinuousIntegrationBuild)' == 'true'">true</TreatWarningsAsErrors>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<ItemGroup>
@@ -20,12 +21,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.363">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="QRCoder" Version="1.6.0" />
<PackageReference Include="SteamKit2" Version="3.0.0-Beta.4" />
<PackageReference Include="protobuf-net" Version="3.4.30" />
<PackageReference Include="QRCoder" Version="1.8.1" />
<PackageReference Include="SteamKit2" Version="3.4.0" />
</ItemGroup>
</Project>
+2 -1
View File
@@ -10,6 +10,7 @@ namespace DepotDownloader
{
public int CellID { get; set; }
public bool DownloadAllPlatforms { get; set; }
public bool DownloadAllArchs { get; set; }
public bool DownloadAllLanguages { get; set; }
public bool DownloadManifestOnly { get; set; }
public string InstallDirectory { get; set; }
@@ -22,7 +23,6 @@ namespace DepotDownloader
public bool VerifyAll { get; set; }
public int MaxServers { get; set; }
public int MaxDownloads { get; set; }
public bool RememberPassword { get; set; }
@@ -31,5 +31,6 @@ namespace DepotDownloader
public uint? LoginID { get; set; }
public bool UseQrCode { get; set; }
public bool SkipAppConfirmation { get; set; }
}
}
+3
View File
@@ -1,2 +1,5 @@
GetConsoleMode
GetConsoleProcessList
GetStdHandle
MessageBox
SetConsoleMode
-1
View File
@@ -1,7 +1,6 @@
// This file is subject to the terms and conditions defined
// in file 'LICENSE', which is part of this source code package.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
+118 -9
View File
@@ -11,11 +11,14 @@ using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SteamKit2;
using SteamKit2.CDN;
namespace DepotDownloader
{
class Program
{
private static bool[] consumedArgs;
static async Task<int> Main(string[] args)
{
if (args.Length == 0)
@@ -46,6 +49,8 @@ namespace DepotDownloader
return 0;
}
consumedArgs = new bool[args.Length];
if (HasParameter(args, "-debug"))
{
PrintVersion(true);
@@ -63,6 +68,21 @@ namespace DepotDownloader
var password = GetParameter<string>(args, "-password") ?? GetParameter<string>(args, "-pass");
ContentDownloader.Config.RememberPassword = HasParameter(args, "-remember-password");
ContentDownloader.Config.UseQrCode = HasParameter(args, "-qr");
ContentDownloader.Config.SkipAppConfirmation = HasParameter(args, "-no-mobile");
if (username == null)
{
if (ContentDownloader.Config.RememberPassword && !ContentDownloader.Config.UseQrCode)
{
Console.WriteLine("Error: -remember-password can not be used without -username or -qr.");
return 1;
}
}
else if (ContentDownloader.Config.UseQrCode)
{
Console.WriteLine("Error: -qr can not be used with -username.");
return 1;
}
ContentDownloader.Config.DownloadManifestOnly = HasParameter(args, "-manifest-only");
@@ -117,9 +137,24 @@ namespace DepotDownloader
ContentDownloader.Config.InstallDirectory = GetParameter<string>(args, "-dir");
ContentDownloader.Config.VerifyAll = HasParameter(args, "-verify-all") || HasParameter(args, "-verify_all") || HasParameter(args, "-validate");
ContentDownloader.Config.MaxServers = GetParameter(args, "-max-servers", 20);
if (HasParameter(args, "-use-lancache"))
{
await Client.DetectLancacheServerAsync();
if (Client.UseLancacheServer)
{
Console.WriteLine("Detected Lancache server! Downloads will be directed through the Lancache.");
// Increasing the number of concurrent downloads when the cache is detected since the downloads will likely
// be served much faster than over the internet. Steam internally has this behavior as well.
if (!HasParameter(args, "-max-downloads"))
{
ContentDownloader.Config.MaxDownloads = 25;
}
}
}
ContentDownloader.Config.MaxDownloads = GetParameter(args, "-max-downloads", 8);
ContentDownloader.Config.MaxServers = Math.Max(ContentDownloader.Config.MaxServers, ContentDownloader.Config.MaxDownloads);
ContentDownloader.Config.LoginID = HasParameter(args, "-loginid") ? GetParameter<uint>(args, "-loginid") : null;
#endregion
@@ -137,6 +172,8 @@ namespace DepotDownloader
{
#region Pubfile Downloading
PrintUnconsumedArgs(args);
if (InitializeSteam(username, password))
{
try
@@ -172,6 +209,8 @@ namespace DepotDownloader
{
#region UGC Downloading
PrintUnconsumedArgs(args);
if (InitializeSteam(username, password))
{
try
@@ -208,9 +247,16 @@ namespace DepotDownloader
#region App downloading
var branch = GetParameter<string>(args, "-branch") ?? GetParameter<string>(args, "-beta") ?? ContentDownloader.DEFAULT_BRANCH;
ContentDownloader.Config.BetaPassword = GetParameter<string>(args, "-betapassword");
ContentDownloader.Config.BetaPassword = GetParameter<string>(args, "-branchpassword") ?? GetParameter<string>(args, "-betapassword");
if (!string.IsNullOrEmpty(ContentDownloader.Config.BetaPassword) && string.IsNullOrEmpty(branch))
{
Console.WriteLine("Error: Cannot specify -branchpassword when -branch is not specified.");
return 1;
}
ContentDownloader.Config.DownloadAllPlatforms = HasParameter(args, "-all-platforms");
var os = GetParameter<string>(args, "-os");
if (ContentDownloader.Config.DownloadAllPlatforms && !string.IsNullOrEmpty(os))
@@ -219,8 +265,16 @@ namespace DepotDownloader
return 1;
}
ContentDownloader.Config.DownloadAllArchs = HasParameter(args, "-all-archs");
var arch = GetParameter<string>(args, "-osarch");
if (ContentDownloader.Config.DownloadAllArchs && !string.IsNullOrEmpty(arch))
{
Console.WriteLine("Error: Cannot specify -osarch when -all-archs is specified.");
return 1;
}
ContentDownloader.Config.DownloadAllLanguages = HasParameter(args, "-all-languages");
var language = GetParameter<string>(args, "-language");
@@ -253,6 +307,8 @@ namespace DepotDownloader
depotManifestIds.AddRange(depotIdList.Select(depotId => (depotId, ContentDownloader.INVALID_MANIFEST_ID)));
}
PrintUnconsumedArgs(args);
if (InitializeSteam(username, password))
{
try
@@ -294,6 +350,11 @@ namespace DepotDownloader
{
if (username != null && password == null && (!ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginTokens.ContainsKey(username)))
{
if (AccountSettingsStore.Instance.LoginTokens.ContainsKey(username))
{
Console.WriteLine($"Account \"{username}\" has stored credentials. Did you forget to specify -remember-password?");
}
do
{
Console.Write("Enter account password for \"{0}\": ", username);
@@ -316,6 +377,21 @@ namespace DepotDownloader
}
}
if (!string.IsNullOrEmpty(password))
{
const int MAX_PASSWORD_SIZE = 64;
if (password.Length > MAX_PASSWORD_SIZE)
{
Console.Error.WriteLine($"Warning: Password is longer than {MAX_PASSWORD_SIZE} characters, which is not supported by Steam.");
}
if (!password.All(char.IsAscii))
{
Console.Error.WriteLine("Warning: Password contains non-ASCII characters, which is not supported by Steam.");
}
}
return ContentDownloader.InitializeSteam3(username, password);
}
@@ -324,7 +400,10 @@ namespace DepotDownloader
for (var x = 0; x < args.Length; ++x)
{
if (args[x].Equals(param, StringComparison.OrdinalIgnoreCase))
{
consumedArgs[x] = true;
return x;
}
}
return -1;
@@ -347,6 +426,7 @@ namespace DepotDownloader
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
consumedArgs[index + 1] = true;
return (T)converter.ConvertFromString(strParam);
}
@@ -372,6 +452,7 @@ namespace DepotDownloader
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
consumedArgs[index] = true;
list.Add((T)converter.ConvertFromString(strParam));
}
@@ -381,6 +462,26 @@ namespace DepotDownloader
return list;
}
static void PrintUnconsumedArgs(string[] args)
{
var printError = false;
for (var index = 0; index < consumedArgs.Length; index++)
{
if (!consumedArgs[index])
{
printError = true;
Console.Error.WriteLine($"Argument #{index + 1} {args[index]} was not used.");
}
}
if (printError)
{
Console.Error.WriteLine("Make sure you specified the arguments correctly. Check --help for correct arguments.");
Console.Error.WriteLine();
}
}
static void PrintUsage()
{
// Do not use tabs to align parameters here because tab size may differ
@@ -398,9 +499,10 @@ namespace DepotDownloader
Console.WriteLine(" -app <#> - the AppID to download.");
Console.WriteLine(" -depot <#> - the DepotID to download.");
Console.WriteLine(" -manifest <id> - manifest id of content to download (requires -depot, default: current for branch).");
Console.WriteLine($" -beta <branchname> - download from specified branch if available (default: {ContentDownloader.DEFAULT_BRANCH}).");
Console.WriteLine(" -betapassword <pass> - branch password if applicable.");
Console.WriteLine($" -branch <branchname> - download from specified branch if available (default: {ContentDownloader.DEFAULT_BRANCH}).");
Console.WriteLine(" -branchpassword <pass> - branch password if applicable.");
Console.WriteLine(" -all-platforms - downloads all platform-specific depots when -app is used.");
Console.WriteLine(" -all-archs - download all architecture-specific depots when -app is used.");
Console.WriteLine(" -os <os> - the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)");
Console.WriteLine(" -osarch <arch> - the architecture for which to download the game (32 or 64, default: the host's architecture)");
Console.WriteLine(" -all-languages - download all language-specific depots when -app is used.");
@@ -412,17 +514,24 @@ namespace DepotDownloader
Console.WriteLine();
Console.WriteLine(" -username <user> - the username of the account to login to for restricted content.");
Console.WriteLine(" -password <pass> - the password of the account to login to for restricted content.");
Console.WriteLine(" -remember-password - if set, remember the password for subsequent logins of this user. (Use -username <username> -remember-password as login credentials)");
Console.WriteLine(" -remember-password - if set, remember the password for subsequent logins of this user.");
Console.WriteLine(" use -username <username> -remember-password as login credentials.");
Console.WriteLine(" -qr - display a login QR code to be scanned with the Steam mobile app");
Console.WriteLine(" -no-mobile - prefer entering a 2FA code instead of prompting to accept in the Steam mobile app");
Console.WriteLine();
Console.WriteLine(" -dir <installdir> - the directory in which to place downloaded files.");
Console.WriteLine(" -filelist <file.txt> - a list of files to download (from the manifest). Prefix file path with 'regex:' if you want to match with regex.");
Console.WriteLine(" -validate - Include checksum verification of files already downloaded");
Console.WriteLine(" -filelist <file.txt> - the name of a local file that contains a list of files to download (from the manifest).");
Console.WriteLine(" prefix file path with `regex:` if you want to match with regex. each file path should be on their own line.");
Console.WriteLine();
Console.WriteLine(" -validate - include checksum verification of files already downloaded");
Console.WriteLine(" -manifest-only - downloads a human readable manifest for any depots that would be downloaded.");
Console.WriteLine(" -cellid <#> - the overridden CellID of the content server to download from.");
Console.WriteLine(" -max-servers <#> - maximum number of content servers to use. (default: 20).");
Console.WriteLine(" -max-downloads <#> - maximum number of chunks to download concurrently. (default: 8).");
Console.WriteLine(" -loginid <#> - a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently.");
Console.WriteLine(" -use-lancache - forces downloads over the local network via a Lancache instance.");
Console.WriteLine();
Console.WriteLine(" -debug - enable verbose debug logging.");
Console.WriteLine(" -V or --version - print version and runtime.");
}
static void PrintVersion(bool printExtra = false)
+35
View File
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using ProtoBuf;
using SteamKit2;
@@ -157,5 +158,39 @@ namespace DepotDownloader
using var ds = new DeflateStream(fs, CompressionMode.Compress);
ms.CopyTo(ds);
}
public DepotManifest ConvertToSteamManifest(uint depotId)
{
ulong uncompressedSize = 0, compressedSize = 0;
var newManifest = new DepotManifest();
newManifest.Files = new List<DepotManifest.FileData>(Files.Count);
foreach (var file in Files)
{
var fileNameHash = SHA1.HashData(Encoding.UTF8.GetBytes(file.FileName.Replace('/', '\\').ToLowerInvariant()));
var newFile = new DepotManifest.FileData(file.FileName, fileNameHash, file.Flags, file.TotalSize, file.FileHash, null, false, file.Chunks.Count);
foreach (var chunk in file.Chunks)
{
var newChunk = new DepotManifest.ChunkData(chunk.ChunkID, BitConverter.ToUInt32(chunk.Checksum, 0), chunk.Offset, chunk.CompressedLength, chunk.UncompressedLength);
newFile.Chunks.Add(newChunk);
uncompressedSize += chunk.UncompressedLength;
compressedSize += chunk.CompressedLength;
}
newManifest.Files.Add(newFile);
}
newManifest.FilenamesEncrypted = false;
newManifest.DepotID = depotId;
newManifest.ManifestGID = ID;
newManifest.CreationTime = CreationTime;
newManifest.TotalUncompressedSize = uncompressedSize;
newManifest.TotalCompressedSize = compressedSize;
newManifest.EncryptedCRC = 0;
return newManifest;
}
}
}
+152 -184
View File
@@ -39,12 +39,11 @@ namespace DepotDownloader
public SteamContent steamContent;
readonly SteamApps steamApps;
readonly SteamCloud steamCloud;
readonly SteamUnifiedMessages.UnifiedService<IPublishedFile> steamPublishedFile;
readonly PublishedFile steamPublishedFile;
readonly CallbackManager callbacks;
readonly bool authenticatedUser;
bool bConnected;
bool bConnecting;
bool bAborted;
bool bExpectingDisconnectRemote;
@@ -52,15 +51,12 @@ namespace DepotDownloader
bool bIsConnectionRecovery;
int connectionBackoff;
int seq; // more hack fixes
DateTime connectTime;
AuthSession authSession;
readonly CancellationTokenSource abortedToken = new();
// input
readonly SteamUser.LogOnDetails logonDetails;
static readonly TimeSpan STEAM3_TIMEOUT = TimeSpan.FromSeconds(30);
public Steam3Session(SteamUser.LogOnDetails details)
{
this.logonDetails = details;
@@ -68,7 +64,7 @@ namespace DepotDownloader
var clientConfiguration = SteamConfiguration.Create(config =>
config
.WithHttpClientFactory(HttpClientFactory.CreateHttpClient)
.WithHttpClientFactory(static purpose => HttpClientFactory.CreateHttpClient())
);
this.steamClient = new SteamClient(clientConfiguration);
@@ -77,7 +73,7 @@ namespace DepotDownloader
this.steamApps = this.steamClient.GetHandler<SteamApps>();
this.steamCloud = this.steamClient.GetHandler<SteamCloud>();
var steamUnifiedMessages = this.steamClient.GetHandler<SteamUnifiedMessages>();
this.steamPublishedFile = steamUnifiedMessages.CreateService<IPublishedFile>();
this.steamPublishedFile = steamUnifiedMessages.CreateService<PublishedFile>();
this.steamContent = this.steamClient.GetHandler<SteamContent>();
this.callbacks = new CallbackManager(this.steamClient);
@@ -93,7 +89,7 @@ namespace DepotDownloader
public delegate bool WaitCondition();
private readonly object steamLock = new();
private readonly Lock steamLock = new();
public bool WaitUntilCallback(Action submitter, WaitCondition waiter)
{
@@ -109,7 +105,7 @@ namespace DepotDownloader
{
lock (steamLock)
{
WaitForCallbacks();
callbacks.RunWaitCallbacks(TimeSpan.FromSeconds(1));
}
} while (!bAborted && this.seq == seq && !waiter());
}
@@ -127,36 +123,51 @@ namespace DepotDownloader
return IsLoggedOn;
}
public void RequestAppInfo(uint appId, bool bForce = false)
public async Task TickCallbacks()
{
var token = abortedToken.Token;
try
{
while (!token.IsCancellationRequested)
{
await callbacks.RunWaitCallbackAsync(token);
}
}
catch (OperationCanceledException)
{
//
}
}
public async Task RequestAppInfo(uint appId, bool bForce = false)
{
if ((AppInfo.ContainsKey(appId) && !bForce) || bAborted)
return;
var completed = false;
Action<SteamApps.PICSTokensCallback> cbMethodTokens = appTokens =>
var appTokens = await steamApps.PICSGetAccessTokens([appId], []);
if (appTokens.AppTokensDenied.Contains(appId))
{
completed = true;
if (appTokens.AppTokensDenied.Contains(appId))
{
Console.WriteLine("Insufficient privileges to get access token for app {0}", appId);
}
Console.WriteLine("Insufficient privileges to get access token for app {0}", appId);
}
foreach (var token_dict in appTokens.AppTokens)
{
this.AppTokens[token_dict.Key] = token_dict.Value;
}
};
WaitUntilCallback(() =>
foreach (var token_dict in appTokens.AppTokens)
{
callbacks.Subscribe(steamApps.PICSGetAccessTokens(new List<uint> { appId }, new List<uint>()), cbMethodTokens);
}, () => { return completed; });
this.AppTokens[token_dict.Key] = token_dict.Value;
}
completed = false;
Action<SteamApps.PICSProductInfoCallback> cbMethod = appInfo =>
var request = new SteamApps.PICSRequest(appId);
if (AppTokens.TryGetValue(appId, out var token))
{
completed = !appInfo.ResponsePending;
request.AccessToken = token;
}
var appInfoMultiple = await steamApps.PICSGetProductInfo([request], []);
foreach (var appInfo in appInfoMultiple.Results)
{
foreach (var app_value in appInfo.Apps)
{
var app = app_value.Value;
@@ -169,45 +180,17 @@ namespace DepotDownloader
{
AppInfo[app] = null;
}
};
var request = new SteamApps.PICSRequest(appId);
if (AppTokens.TryGetValue(appId, out var token))
{
request.AccessToken = token;
}
WaitUntilCallback(() =>
{
callbacks.Subscribe(steamApps.PICSGetProductInfo(new List<SteamApps.PICSRequest> { request }, new List<SteamApps.PICSRequest>()), cbMethod);
}, () => { return completed; });
}
public void RequestPackageInfo(IEnumerable<uint> packageIds)
public async Task RequestPackageInfo(IEnumerable<uint> packageIds)
{
var packages = packageIds.ToList();
packages.RemoveAll(pid => PackageInfo.ContainsKey(pid));
packages.RemoveAll(PackageInfo.ContainsKey);
if (packages.Count == 0 || bAborted)
return;
var completed = false;
Action<SteamApps.PICSProductInfoCallback> cbMethod = packageInfo =>
{
completed = !packageInfo.ResponsePending;
foreach (var package_value in packageInfo.Packages)
{
var package = package_value.Value;
PackageInfo[package.ID] = package;
}
foreach (var package in packageInfo.UnknownPackages)
{
PackageInfo[package] = null;
}
};
var packageRequests = new List<SteamApps.PICSRequest>();
foreach (var package in packages)
@@ -222,55 +205,53 @@ namespace DepotDownloader
packageRequests.Add(request);
}
WaitUntilCallback(() =>
var packageInfoMultiple = await steamApps.PICSGetProductInfo([], packageRequests);
foreach (var packageInfo in packageInfoMultiple.Results)
{
callbacks.Subscribe(steamApps.PICSGetProductInfo(new List<SteamApps.PICSRequest>(), packageRequests), cbMethod);
}, () => { return completed; });
foreach (var package_value in packageInfo.Packages)
{
var package = package_value.Value;
PackageInfo[package.ID] = package;
}
foreach (var package in packageInfo.UnknownPackages)
{
PackageInfo[package] = null;
}
}
}
public bool RequestFreeAppLicense(uint appId)
public async Task<bool> RequestFreeAppLicense(uint appId)
{
var success = false;
var completed = false;
Action<SteamApps.FreeLicenseCallback> cbMethod = resultInfo =>
try
{
completed = true;
success = resultInfo.GrantedApps.Contains(appId);
};
var resultInfo = await steamApps.RequestFreeLicense(appId);
WaitUntilCallback(() =>
return resultInfo.GrantedApps.Contains(appId);
}
catch (Exception ex)
{
callbacks.Subscribe(steamApps.RequestFreeLicense(appId), cbMethod);
}, () => { return completed; });
return success;
Console.WriteLine($"Failed to request FreeOnDemand license for app {appId}: {ex.Message}");
return false;
}
}
public void RequestDepotKey(uint depotId, uint appid = 0)
public async Task RequestDepotKey(uint depotId, uint appid = 0)
{
if (DepotKeys.ContainsKey(depotId) || bAborted)
return;
var completed = false;
var depotKey = await steamApps.GetDepotDecryptionKey(depotId, appid);
Action<SteamApps.DepotKeyCallback> cbMethod = depotKey =>
Console.WriteLine("Got depot key for {0} result: {1}", depotKey.DepotID, depotKey.Result);
if (depotKey.Result != EResult.OK)
{
completed = true;
Console.WriteLine("Got depot key for {0} result: {1}", depotKey.DepotID, depotKey.Result);
return;
}
if (depotKey.Result != EResult.OK)
{
Abort();
return;
}
DepotKeys[depotKey.DepotID] = depotKey.DepotKey;
};
WaitUntilCallback(() =>
{
callbacks.Subscribe(steamApps.GetDepotDecryptionKey(depotId, appid), cbMethod);
}, () => { return completed; });
DepotKeys[depotKey.DepotID] = depotKey.DepotKey;
}
@@ -281,9 +262,19 @@ namespace DepotDownloader
var requestCode = await steamContent.GetManifestRequestCode(depotId, appId, manifestId, branch);
Console.WriteLine("Got manifest request code for {0} {1} result: {2}",
depotId, manifestId,
requestCode);
if (requestCode == 0)
{
Console.WriteLine($"No manifest request code was returned for depot {depotId} from app {appId}, manifest {manifestId}");
if (!authenticatedUser)
{
Console.WriteLine("Suggestion: Try logging in with -username as old manifests may not be available for anonymous accounts.");
}
}
else
{
Console.WriteLine($"Got manifest request code for depot {depotId} from app {appId}, manifest {manifestId}, result: {requestCode}");
}
return requestCode;
}
@@ -312,86 +303,68 @@ namespace DepotDownloader
completion.TrySetResult(cdnAuth);
}
public void CheckAppBetaPassword(uint appid, string password)
public async Task CheckAppBetaPassword(uint appid, string password)
{
var completed = false;
Action<SteamApps.CheckAppBetaPasswordCallback> cbMethod = appPassword =>
var appPassword = await steamApps.CheckAppBetaPassword(appid, password);
Console.WriteLine("Retrieved {0} beta keys with result: {1}", appPassword.BetaPasswords.Count, appPassword.Result);
foreach (var entry in appPassword.BetaPasswords)
{
completed = true;
Console.WriteLine("Retrieved {0} beta keys with result: {1}", appPassword.BetaPasswords.Count, appPassword.Result);
foreach (var entry in appPassword.BetaPasswords)
{
AppBetaPasswords[entry.Key] = entry.Value;
}
};
WaitUntilCallback(() =>
{
callbacks.Subscribe(steamApps.CheckAppBetaPassword(appid, password), cbMethod);
}, () => { return completed; });
AppBetaPasswords[entry.Key] = entry.Value;
}
}
public PublishedFileDetails GetPublishedFileDetails(uint appId, PublishedFileID pubFile)
public async Task<KeyValue> GetPrivateBetaDepotSection(uint appid, string branch)
{
var pubFileRequest = new CPublishedFile_GetDetails_Request { appid = appId };
if (!AppBetaPasswords.TryGetValue(branch, out var branchPassword)) // Should be filled by CheckAppBetaPassword
{
return new KeyValue();
}
AppTokens.TryGetValue(appid, out var accessToken); // Should be filled by RequestAppInfo
var privateBeta = await steamApps.PICSGetPrivateBeta(appid, accessToken, branch, branchPassword);
Console.WriteLine($"Retrieved private beta depot section for {appid} with result: {privateBeta.Result}");
return privateBeta.DepotSection;
}
public async Task<PublishedFileDetails> GetPublishedFileDetails(uint appId, PublishedFileID pubFile)
{
var pubFileRequest = new CPublishedFile_GetDetails_Request
{
appid = appId,
includechildren = true,
};
pubFileRequest.publishedfileids.Add(pubFile);
var completed = false;
PublishedFileDetails details = null;
var details = await steamPublishedFile.GetDetails(pubFileRequest);
Action<SteamUnifiedMessages.ServiceMethodResponse> cbMethod = callback =>
if (details.Result == EResult.OK)
{
completed = true;
if (callback.Result == EResult.OK)
{
var response = callback.GetDeserializedResponse<CPublishedFile_GetDetails_Response>();
details = response.publishedfiledetails.FirstOrDefault();
}
else
{
throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving file details for pubfile {pubFile}.");
}
};
return details.Body.publishedfiledetails.FirstOrDefault();
}
WaitUntilCallback(() =>
{
callbacks.Subscribe(steamPublishedFile.SendMessage(api => api.GetDetails(pubFileRequest)), cbMethod);
}, () => { return completed; });
return details;
throw new Exception($"EResult {(int)details.Result} ({details.Result}) while retrieving file details for pubfile {pubFile}.");
}
public SteamCloud.UGCDetailsCallback GetUGCDetails(UGCHandle ugcHandle)
public async Task<SteamCloud.UGCDetailsCallback> GetUGCDetails(UGCHandle ugcHandle)
{
var completed = false;
SteamCloud.UGCDetailsCallback details = null;
var callback = await steamCloud.RequestUGCDetails(ugcHandle);
Action<SteamCloud.UGCDetailsCallback> cbMethod = callback =>
if (callback.Result == EResult.OK)
{
completed = true;
if (callback.Result == EResult.OK)
{
details = callback;
}
else if (callback.Result == EResult.FileNotFound)
{
details = null;
}
else
{
throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC details for {ugcHandle}.");
}
};
WaitUntilCallback(() =>
return callback;
}
else if (callback.Result == EResult.FileNotFound)
{
callbacks.Subscribe(steamCloud.RequestUGCDetails(ugcHandle), cbMethod);
}, () => { return completed; });
return null;
}
return details;
throw new Exception($"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC details for {ugcHandle}.");
}
private void ResetConnectionFlags()
@@ -404,14 +377,11 @@ namespace DepotDownloader
void Connect()
{
bAborted = false;
bConnected = false;
bConnecting = true;
connectionBackoff = 0;
authSession = null;
ResetConnectionFlags();
this.connectTime = DateTime.Now;
this.steamClient.Connect();
}
@@ -428,9 +398,9 @@ namespace DepotDownloader
}
bAborted = true;
bConnected = false;
bConnecting = false;
bIsConnectionRecovery = false;
abortedToken.Cancel();
steamClient.Disconnect();
Ansi.Progress(Ansi.ProgressState.Hidden);
@@ -448,28 +418,13 @@ namespace DepotDownloader
steamClient.Disconnect();
}
private void WaitForCallbacks()
{
callbacks.RunWaitCallbacks(TimeSpan.FromSeconds(1));
var diff = DateTime.Now - connectTime;
if (diff > STEAM3_TIMEOUT && !bConnected)
{
Console.WriteLine("Timeout connecting to Steam3.");
Abort();
}
}
private async void ConnectedCallback(SteamClient.ConnectedCallback connected)
{
Console.WriteLine(" Done!");
bConnecting = false;
bConnected = true;
// Update our tracking so that we don't time out, even if we need to reconnect multiple times,
// e.g. if the authentication phase takes a while and therefore multiple connections.
connectTime = DateTime.Now;
connectionBackoff = 0;
if (!authenticatedUser)
@@ -491,13 +446,14 @@ namespace DepotDownloader
try
{
_ = AccountSettingsStore.Instance.GuardData.TryGetValue(logonDetails.Username, out var guarddata);
authSession = await steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new SteamKit2.Authentication.AuthSessionDetails
authSession = await steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new AuthSessionDetails
{
DeviceFriendlyName = nameof(DepotDownloader),
Username = logonDetails.Username,
Password = logonDetails.Password,
IsPersistentSession = ContentDownloader.Config.RememberPassword,
GuardData = guarddata,
Authenticator = new UserConsoleAuthenticator(),
Authenticator = new ConsoleAuthenticator(),
});
}
catch (TaskCanceledException)
@@ -519,8 +475,8 @@ namespace DepotDownloader
{
var session = await steamClient.Authentication.BeginAuthSessionViaQRAsync(new AuthSessionDetails
{
DeviceFriendlyName = nameof(DepotDownloader),
IsPersistentSession = ContentDownloader.Config.RememberPassword,
Authenticator = new UserConsoleAuthenticator(),
});
authSession = session;
@@ -563,11 +519,17 @@ namespace DepotDownloader
if (result.NewGuardData != null)
{
AccountSettingsStore.Instance.GuardData[result.AccountName] = result.NewGuardData;
if (ContentDownloader.Config.UseQrCode)
{
Console.WriteLine($"Success! Next time you can login with -username {result.AccountName} -remember-password instead of -qr.");
}
}
else
{
AccountSettingsStore.Instance.GuardData.Remove(result.AccountName);
}
AccountSettingsStore.Instance.LoginTokens[result.AccountName] = result.RefreshToken;
AccountSettingsStore.Save();
}
@@ -610,16 +572,18 @@ namespace DepotDownloader
}
else if (!bAborted)
{
connectionBackoff += 1;
if (bConnecting)
{
Console.WriteLine("Connection to Steam failed. Trying again");
Console.WriteLine($"Connection to Steam failed. Trying again (#{connectionBackoff})...");
}
else
{
Console.WriteLine("Lost connection to Steam. Reconnecting");
}
Thread.Sleep(1000 * ++connectionBackoff);
Thread.Sleep(1000 * connectionBackoff);
// Any connection related flags need to be reset here to match the state after Connect
ResetConnectionFlags();
@@ -746,10 +710,14 @@ namespace DepotDownloader
using var qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode(challengeUrl, QRCodeGenerator.ECCLevel.L);
using var qrCode = new AsciiQRCode(qrCodeData);
var qrCodeAsAsciiArt = qrCode.GetGraphic(1, drawQuietZones: false);
var qrCodeAsAsciiArt = qrCode.GetLineByLineGraphic(1, drawQuietZones: true);
Console.WriteLine("Use the Steam Mobile App to sign in with this QR code:");
Console.WriteLine(qrCodeAsAsciiArt);
foreach (var line in qrCodeAsAsciiArt)
{
Console.WriteLine(line);
}
}
}
}
+98 -35
View File
@@ -9,6 +9,7 @@ using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using SteamKit2;
namespace DepotDownloader
{
@@ -78,16 +79,16 @@ namespace DepotDownloader
}
// Validate a file against Steam3 Chunk data
public static List<ProtoManifest.ChunkData> ValidateSteam3FileChecksums(FileStream fs, ProtoManifest.ChunkData[] chunkdata)
public static List<DepotManifest.ChunkData> ValidateSteam3FileChecksums(FileStream fs, DepotManifest.ChunkData[] chunkdata)
{
var neededChunks = new List<ProtoManifest.ChunkData>();
var neededChunks = new List<DepotManifest.ChunkData>();
foreach (var data in chunkdata)
{
fs.Seek((long)data.Offset, SeekOrigin.Begin);
var adler = AdlerHash(fs, (int)data.UncompressedLength);
if (!adler.SequenceEqual(data.Checksum))
if (!adler.SequenceEqual(BitConverter.GetBytes(data.Checksum)))
{
neededChunks.Add(data);
}
@@ -110,6 +111,100 @@ namespace DepotDownloader
return BitConverter.GetBytes(a | (b << 16));
}
public static byte[] FileSHAHash(string filename)
{
using (var fs = File.Open(filename, FileMode.Open))
using (var sha = SHA1.Create())
{
var output = sha.ComputeHash(fs);
return output;
}
}
public static DepotManifest LoadManifestFromFile(string directory, uint depotId, ulong manifestId, bool badHashWarning)
{
// Try loading Steam format manifest first.
var filename = Path.Combine(directory, string.Format("{0}_{1}.manifest", depotId, manifestId));
if (File.Exists(filename))
{
byte[] expectedChecksum;
try
{
expectedChecksum = File.ReadAllBytes(filename + ".sha");
}
catch (IOException)
{
expectedChecksum = null;
}
var currentChecksum = FileSHAHash(filename);
if (expectedChecksum != null && expectedChecksum.SequenceEqual(currentChecksum))
{
return DepotManifest.LoadFromFile(filename);
}
else if (badHashWarning)
{
Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", manifestId);
}
}
// Try converting legacy manifest format.
filename = Path.Combine(directory, string.Format("{0}_{1}.bin", depotId, manifestId));
if (File.Exists(filename))
{
byte[] expectedChecksum;
try
{
expectedChecksum = File.ReadAllBytes(filename + ".sha");
}
catch (IOException)
{
expectedChecksum = null;
}
byte[] currentChecksum;
var oldManifest = ProtoManifest.LoadFromFile(filename, out currentChecksum);
if (oldManifest != null && (expectedChecksum == null || !expectedChecksum.SequenceEqual(currentChecksum)))
{
oldManifest = null;
if (badHashWarning)
{
Console.WriteLine("Manifest {0} on disk did not match the expected checksum.", manifestId);
}
}
if (oldManifest != null)
{
return oldManifest.ConvertToSteamManifest(depotId);
}
}
return null;
}
public static bool SaveManifestToFile(string directory, DepotManifest manifest)
{
try
{
var filename = Path.Combine(directory, string.Format("{0}_{1}.manifest", manifest.DepotID, manifest.ManifestGID));
manifest.SaveToFile(filename);
File.WriteAllBytes(filename + ".sha", FileSHAHash(filename));
return true; // If serialization completes without throwing an exception, return true
}
catch (Exception)
{
return false; // Return false if an error occurs
}
}
public static byte[] DecodeHexString(string hex)
{
if (hex == null)
@@ -140,37 +235,5 @@ namespace DepotDownloader
return output;
}
public static async Task InvokeAsync(IEnumerable<Func<Task>> taskFactories, int maxDegreeOfParallelism)
{
ArgumentNullException.ThrowIfNull(taskFactories);
ArgumentOutOfRangeException.ThrowIfLessThanOrEqual(maxDegreeOfParallelism, 0);
var queue = taskFactories.ToArray();
if (queue.Length == 0)
{
return;
}
var tasksInFlight = new List<Task>(maxDegreeOfParallelism);
var index = 0;
do
{
while (tasksInFlight.Count < maxDegreeOfParallelism && index < queue.Length)
{
var taskFactory = queue[index++];
tasksInFlight.Add(taskFactory());
}
var completedTask = await Task.WhenAny(tasksInFlight).ConfigureAwait(false);
await completedTask.ConfigureAwait(false);
tasksInFlight.Remove(completedTask);
} while (index < queue.Length || tasksInFlight.Count != 0);
}
}
}
+54 -24
View File
@@ -61,33 +61,54 @@ For example: `./DepotDownloader -app 730 -ugc 770604181014286929`
## Parameters
#### Authentication
Parameter | Description
----------------------- | -----------
`-app <#>` | the AppID to download.
`-depot <#>` | the DepotID to download.
`-manifest <id>` | manifest id of content to download (requires `-depot`, default: current for branch).
`-ugc <#>` | the UGC ID to download.
`-beta <branchname>` | download from specified branch if available (default: Public).
`-betapassword <pass>` | branch password if applicable.
`-all-platforms` | downloads all platform-specific depots when `-app` is used.
`-os <os>` | the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)
`-osarch <arch>` | the architecture for which to download the game (32 or 64, default: the host's architecture)
`-all-languages` | download all language-specific depots when `-app` is used.
`-language <lang>` | the language for which to download the game (default: english)
`-lowviolence` | download low violence depots when `-app` is used.
`-pubfile <#>` | the PublishedFileId to download. (Will automatically resolve to UGC id)
`-username <user>` | the username of the account to login to for restricted content.
`-password <pass>` | the password of the account to login to for restricted content.
`-remember-password` | if set, remember the password for subsequent logins of this user. (Use `-username <username> -remember-password` as login credentials)
`-username <user>` | the username of the account to login to for restricted content.
`-password <pass>` | the password of the account to login to for restricted content.
`-remember-password` | if set, remember the password for subsequent logins of this user. (Use `-username <username> -remember-password` as login credentials)
`-qr` | display a login QR code to be scanned with the Steam mobile app
`-no-mobile` | prefer entering a 2FA code instead of prompting to accept in the Steam mobile app.
`-loginid <#>` | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently.
#### Downloading
Parameter | Description
------------------------ | -----------
`-app <#>` | the AppID to download.
`-depot <#>` | the DepotID to download.
`-manifest <id>` | manifest id of content to download (requires `-depot`, default: current for branch).
`-ugc <#>` | the UGC ID to download.
`-pubfile <#>` | the PublishedFileId to download. (Will automatically resolve to UGC id)
`-branch <branchname>` | download from specified branch if available (default: Public).
`-branchpassword <pass>` | branch password if applicable.
#### Download configuration
Parameter | Description
----------------------- | -----------
`-all-platforms` | downloads all platform-specific depots when `-app` is used.
`-os <os>` | the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)
`-osarch <arch>` | the architecture for which to download the game (32 or 64, default: the host's architecture)
`-all-archs` | download all architecture-specific depots when `-app` is used.
`-all-languages` | download all language-specific depots when `-app` is used.
`-language <lang>` | the language for which to download the game (default: english)
`-lowviolence` | download low violence depots when `-app` is used.
`-dir <installdir>` | the directory in which to place downloaded files.
`-filelist <file.txt>` | a list of files to download (from the manifest). Prefix file path with `regex:` if you want to match with regex.
`-validate` | Include checksum verification of files already downloaded
`-manifest-only` | downloads a human readable manifest for any depots that would be downloaded.
`-cellid <#>` | the overridden CellID of the content server to download from.
`-max-servers <#>` | maximum number of content servers to use. (default: 20).
`-max-downloads <#>` | maximum number of chunks to download concurrently. (default: 8).
`-loginid <#>` | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently.
`-V` or `--version` | print version and runtime
`-filelist <file.txt>` | the name of a local file that contains a list of files to download (from the manifest). prefix file path with `regex:` if you want to match with regex. each file path should be on their own line.
`-validate` | include checksum verification of files already downloaded.
`-manifest-only` | downloads a human readable manifest for any depots that would be downloaded.
`-cellid <#>` | the overridden CellID of the content server to download from.
`-max-downloads <#>` | maximum number of chunks to download concurrently. (default: 8).
`-use-lancache` | forces downloads over the local network via a Lancache instance.
#### Other
Parameter | Description
----------------------- | -----------
`-debug` | enable verbose debug logging.
`-V` or `--version` | print version and runtime.
## Frequently Asked Questions
@@ -99,3 +120,12 @@ Any connection to Steam will be closed if they share a LoginID. You can specify
### Why doesn't my password containing special characters work? Do I have to specify the password on the command line?
If you pass the `-password` parameter with a password that contains special characters, you will need to escape the command appropriately for the shell you are using. You do not have to include the `-password` parameter on the command line as long as you include a `-username`. You will be prompted to enter your password interactively.
### I am getting error 401 or no manifest code returned for old manifest ids
Try logging in with a Steam account, this may happen when using anonymous account.
Steam allows developers to block downloading old manifests, in which case no manifest code is returned even when parameters appear correct.
### Why am I getting slow download speeds and frequent connection timeouts?
When downloading old builds, cache server may not have the chunks readily available which makes downloading slower.
Try increasing `-max-downloads` to saturate the network more.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"version": "9.0.100",
"rollForward": "latestMinor"
}
}