Merge pull request #7 from mmvanheusden/experiments

add submodule...
This commit is contained in:
Maarten van Heusden
2021-02-07 14:40:10 +01:00
committed by GitHub
31 changed files with 3439 additions and 763 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "DepotDownloader"]
path = DepotDownloader
url = https://github.com/SteamRE/DepotDownloader

View File

@@ -0,0 +1,7 @@
; EditorConfig: http://EditorConfig.org
root = true
[*]
indent_style = space
indent_size = 4

View File

@@ -0,0 +1,37 @@
name: .NET Core CI
on:
push:
paths-ignore:
- '.github/*'
- '.github/*_TEMPLATE/**'
- '*.md'
pull_request:
paths-ignore:
- '.github/*'
- '.github/*_TEMPLATE/**'
- '*.md'
workflow_dispatch:
jobs:
build:
name: .NET on ${{ matrix.os }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
configuration: [Release, Debug]
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
- name: Build
run: dotnet publish -c ${{ matrix.configuration }} -o artifacts
- name: Upload artifact
uses: actions/upload-artifact@v2
if: matrix.configuration == 'Release'
with:
name: DepotDownloader-${{ runner.os }}
path: artifacts
if-no-files-found: error

120
depotdownloader/.gitignore vendored Normal file
View File

@@ -0,0 +1,120 @@
# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
[Bb]in/
[Oo]bj/
# mstest test results
TestResults
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
.vs
# Build results
[Dd]ebug/
[Rr]elease/
x64/
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.log
*.vspscc
*.vssscc
.builds
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish
# Publish Web Output
*.Publish.xml
# NuGet Packages Directory
packages
*.nupkg
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
[Bb]in
[Oo]bj
sql
TestResults
[Tt]est[Rr]esult*
*.Cache
ClientBin
[Ss]tyle[Cc]op.*
~$*
*.dbmdl
Generated_Code #added for RIA/Silverlight projects
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
# third party libs
boost/
google/
zlib/
protobuf/
cryptopp/
# misc
Thumbs.db

View File

@@ -1,748 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v2.0/any",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v2.0": {},
".NETCoreApp,Version=v2.0/any": {
"DepotDownloader/1.0.0": {
"dependencies": {
"SteamKit2": "1.0.0",
"protobuf-net": "2.4.4"
},
"runtime": {
"DepotDownloader.dll": {}
}
},
"Microsoft.NETCore.Platforms/2.1.0": {},
"Microsoft.NETCore.Targets/1.1.0": {},
"Microsoft.Win32.Registry/4.4.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"System.Security.AccessControl": "4.4.0",
"System.Security.Principal.Windows": "4.5.0"
}
},
"protobuf-net/2.4.4": {
"dependencies": {
"System.Reflection.Emit": "4.3.0",
"System.Reflection.Emit.Lightweight": "4.3.0",
"System.Reflection.TypeExtensions": "4.4.0",
"System.ServiceModel.Primitives": "4.5.3",
"System.Xml.XmlSerializer": "4.3.0"
},
"runtime": {
"lib/netstandard2.0/protobuf-net.dll": {
"assemblyVersion": "2.4.0.0",
"fileVersion": "2.4.4.9"
}
}
},
"runtime.any.System.Collections/4.3.0": {
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"runtime.any.System.Globalization/4.3.0": {},
"runtime.any.System.IO/4.3.0": {},
"runtime.any.System.Reflection/4.3.0": {},
"runtime.any.System.Reflection.Extensions/4.3.0": {},
"runtime.any.System.Reflection.Primitives/4.3.0": {},
"runtime.any.System.Resources.ResourceManager/4.3.0": {},
"runtime.any.System.Runtime/4.3.0": {
"dependencies": {
"System.Private.Uri": "4.3.0"
}
},
"runtime.any.System.Runtime.Handles/4.3.0": {},
"runtime.any.System.Runtime.InteropServices/4.3.0": {},
"runtime.any.System.Text.Encoding/4.3.0": {},
"runtime.any.System.Text.Encoding.Extensions/4.3.0": {},
"runtime.any.System.Threading.Tasks/4.3.0": {},
"System.Collections/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Collections": "4.3.0"
}
},
"System.Diagnostics.Debug/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Globalization/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Globalization": "4.3.0"
}
},
"System.IO/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"runtime.any.System.IO": "4.3.0"
}
},
"System.IO.FileSystem/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.IO.FileSystem.Primitives/4.3.0": {
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.Linq/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0"
}
},
"System.Private.ServiceModel/4.5.3": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"System.Reflection.DispatchProxy": "4.5.0",
"System.Security.Principal.Windows": "4.5.0"
}
},
"System.Private.Uri/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Reflection/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.IO": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection": "4.3.0"
}
},
"System.Reflection.DispatchProxy/4.5.0": {
"runtime": {
"lib/netcoreapp2.0/System.Reflection.DispatchProxy.dll": {
"assemblyVersion": "4.0.4.0",
"fileVersion": "4.6.26515.6"
}
}
},
"System.Reflection.Emit/4.3.0": {
"dependencies": {
"System.IO": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.ILGeneration/4.3.0": {
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Emit.Lightweight/4.3.0": {
"dependencies": {
"System.Reflection": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0"
}
},
"System.Reflection.Extensions/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection.Extensions": "4.3.0"
}
},
"System.Reflection.Primitives/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Reflection.Primitives": "4.3.0"
}
},
"System.Reflection.TypeExtensions/4.4.0": {},
"System.Resources.ResourceManager/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Globalization": "4.3.0",
"System.Reflection": "4.3.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Resources.ResourceManager": "4.3.0"
}
},
"System.Runtime/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"runtime.any.System.Runtime": "4.3.0"
}
},
"System.Runtime.Extensions/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0"
}
},
"System.Runtime.Handles/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Runtime.Handles": "4.3.0"
}
},
"System.Runtime.InteropServices/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Reflection": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Handles": "4.3.0",
"runtime.any.System.Runtime.InteropServices": "4.3.0"
}
},
"System.Security.AccessControl/4.4.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"System.Security.Principal.Windows": "4.5.0"
}
},
"System.Security.Principal.Windows/4.5.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0"
},
"runtime": {
"lib/netstandard2.0/System.Security.Principal.Windows.dll": {
"assemblyVersion": "4.1.1.0",
"fileVersion": "4.6.26515.6"
}
}
},
"System.ServiceModel.Primitives/4.5.3": {
"dependencies": {
"System.Private.ServiceModel": "4.5.3"
},
"runtime": {
"lib/netstandard2.0/System.ServiceModel.Primitives.dll": {
"assemblyVersion": "4.5.0.3",
"fileVersion": "4.6.26720.1"
},
"lib/netstandard2.0/System.ServiceModel.dll": {
"assemblyVersion": "4.0.0.0",
"fileVersion": "4.6.26720.1"
}
}
},
"System.Text.Encoding/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Text.Encoding": "4.3.0"
}
},
"System.Text.Encoding.Extensions/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"System.Text.Encoding": "4.3.0",
"runtime.any.System.Text.Encoding.Extensions": "4.3.0"
}
},
"System.Text.RegularExpressions/4.3.0": {
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.Threading/4.3.0": {
"dependencies": {
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Threading.Tasks/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "2.1.0",
"Microsoft.NETCore.Targets": "1.1.0",
"System.Runtime": "4.3.0",
"runtime.any.System.Threading.Tasks": "4.3.0"
}
},
"System.Threading.Tasks.Extensions/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Runtime": "4.3.0",
"System.Threading.Tasks": "4.3.0"
}
},
"System.Xml.ReaderWriter/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.IO.FileSystem": "4.3.0",
"System.IO.FileSystem.Primitives": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Text.Encoding.Extensions": "4.3.0",
"System.Text.RegularExpressions": "4.3.0",
"System.Threading.Tasks": "4.3.0",
"System.Threading.Tasks.Extensions": "4.3.0"
}
},
"System.Xml.XmlDocument/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Text.Encoding": "4.3.0",
"System.Threading": "4.3.0",
"System.Xml.ReaderWriter": "4.3.0"
}
},
"System.Xml.XmlSerializer/4.3.0": {
"dependencies": {
"System.Collections": "4.3.0",
"System.Globalization": "4.3.0",
"System.IO": "4.3.0",
"System.Linq": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Emit": "4.3.0",
"System.Reflection.Emit.ILGeneration": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.4.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Text.RegularExpressions": "4.3.0",
"System.Threading": "4.3.0",
"System.Xml.ReaderWriter": "4.3.0",
"System.Xml.XmlDocument": "4.3.0"
}
},
"SteamKit2/1.0.0": {
"dependencies": {
"Microsoft.Win32.Registry": "4.4.0",
"protobuf-net": "2.4.4"
},
"runtime": {
"SteamKit2.dll": {}
}
}
}
},
"libraries": {
"DepotDownloader/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.NETCore.Platforms/2.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ok+RPAtESz/9MUXeIEz6Lv5XAGQsaNmEYXMsgVALj4D7kqC8gveKWXWXbufLySR2fWrwZf8smyN5RmHu0e4BHA==",
"path": "microsoft.netcore.platforms/2.1.0",
"hashPath": "microsoft.netcore.platforms.2.1.0.nupkg.sha512"
},
"Microsoft.NETCore.Targets/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==",
"path": "microsoft.netcore.targets/1.1.0",
"hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512"
},
"Microsoft.Win32.Registry/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-LJh1PSoqdugrCv7t+k2B919GGdq4PyrntPVlMCMrcz59nsjOstYmtGWSCiFZ3UNpxv2dlsZdcLzJ847uyOlaEA==",
"path": "microsoft.win32.registry/4.4.0",
"hashPath": "microsoft.win32.registry.4.4.0.nupkg.sha512"
},
"protobuf-net/2.4.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-EYZyqfSlroeRVC2cvyONwjFE6ji50d+0gsGXFha+Ij/ZzIBLFd4HJmlytlabSyeqJFz1RCIDvhIoe9+AEyx3bg==",
"path": "protobuf-net/2.4.4",
"hashPath": "protobuf-net.2.4.4.nupkg.sha512"
},
"runtime.any.System.Collections/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-23g6rqftKmovn2cLeGsuHUYm0FD7pdutb0uQMJpZ3qTvq+zHkgmt6J65VtRry4WDGYlmkMa4xDACtaQ94alNag==",
"path": "runtime.any.system.collections/4.3.0",
"hashPath": "runtime.any.system.collections.4.3.0.nupkg.sha512"
},
"runtime.any.System.Globalization/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-sMDBnad4rp4t7GY442Jux0MCUuKL4otn5BK6Ni0ARTXTSpRNBzZ7hpMfKSvnVSED5kYJm96YOWsqV0JH0d2uuw==",
"path": "runtime.any.system.globalization/4.3.0",
"hashPath": "runtime.any.system.globalization.4.3.0.nupkg.sha512"
},
"runtime.any.System.IO/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==",
"path": "runtime.any.system.io/4.3.0",
"hashPath": "runtime.any.system.io.4.3.0.nupkg.sha512"
},
"runtime.any.System.Reflection/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==",
"path": "runtime.any.system.reflection/4.3.0",
"hashPath": "runtime.any.system.reflection.4.3.0.nupkg.sha512"
},
"runtime.any.System.Reflection.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-cPhT+Vqu52+cQQrDai/V91gubXUnDKNRvlBnH+hOgtGyHdC17aQIU64EaehwAQymd7kJA5rSrVRNfDYrbhnzyA==",
"path": "runtime.any.system.reflection.extensions/4.3.0",
"hashPath": "runtime.any.system.reflection.extensions.4.3.0.nupkg.sha512"
},
"runtime.any.System.Reflection.Primitives/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==",
"path": "runtime.any.system.reflection.primitives/4.3.0",
"hashPath": "runtime.any.system.reflection.primitives.4.3.0.nupkg.sha512"
},
"runtime.any.System.Resources.ResourceManager/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Lxb89SMvf8w9p9+keBLyL6H6x/TEmc6QVsIIA0T36IuyOY3kNvIdyGddA2qt35cRamzxF8K5p0Opq4G4HjNbhQ==",
"path": "runtime.any.system.resources.resourcemanager/4.3.0",
"hashPath": "runtime.any.system.resources.resourcemanager.4.3.0.nupkg.sha512"
},
"runtime.any.System.Runtime/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==",
"path": "runtime.any.system.runtime/4.3.0",
"hashPath": "runtime.any.system.runtime.4.3.0.nupkg.sha512"
},
"runtime.any.System.Runtime.Handles/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-GG84X6vufoEzqx8PbeBKheE4srOhimv+yLtGb/JkR3Y2FmoqmueLNFU4Xx8Y67plFpltQSdK74x0qlEhIpv/CQ==",
"path": "runtime.any.system.runtime.handles/4.3.0",
"hashPath": "runtime.any.system.runtime.handles.4.3.0.nupkg.sha512"
},
"runtime.any.System.Runtime.InteropServices/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lBoFeQfxe/4eqjPi46E0LU/YaCMdNkQ8B4MZu/mkzdIAZh8RQ1NYZSj0egrQKdgdvlPFtP4STtob40r4o2DBAw==",
"path": "runtime.any.system.runtime.interopservices/4.3.0",
"hashPath": "runtime.any.system.runtime.interopservices.4.3.0.nupkg.sha512"
},
"runtime.any.System.Text.Encoding/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==",
"path": "runtime.any.system.text.encoding/4.3.0",
"hashPath": "runtime.any.system.text.encoding.4.3.0.nupkg.sha512"
},
"runtime.any.System.Text.Encoding.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NLrxmLsfRrOuVqPWG+2lrQZnE53MLVeo+w9c54EV+TUo4c8rILpsDXfY8pPiOy9kHpUHHP07ugKmtsU3vVW5Jg==",
"path": "runtime.any.system.text.encoding.extensions/4.3.0",
"hashPath": "runtime.any.system.text.encoding.extensions.4.3.0.nupkg.sha512"
},
"runtime.any.System.Threading.Tasks/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==",
"path": "runtime.any.system.threading.tasks/4.3.0",
"hashPath": "runtime.any.system.threading.tasks.4.3.0.nupkg.sha512"
},
"System.Collections/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
"path": "system.collections/4.3.0",
"hashPath": "system.collections.4.3.0.nupkg.sha512"
},
"System.Diagnostics.Debug/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
"path": "system.diagnostics.debug/4.3.0",
"hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512"
},
"System.Globalization/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
"path": "system.globalization/4.3.0",
"hashPath": "system.globalization.4.3.0.nupkg.sha512"
},
"System.IO/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
"path": "system.io/4.3.0",
"hashPath": "system.io.4.3.0.nupkg.sha512"
},
"System.IO.FileSystem/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
"path": "system.io.filesystem/4.3.0",
"hashPath": "system.io.filesystem.4.3.0.nupkg.sha512"
},
"System.IO.FileSystem.Primitives/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
"path": "system.io.filesystem.primitives/4.3.0",
"hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512"
},
"System.Linq/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
"path": "system.linq/4.3.0",
"hashPath": "system.linq.4.3.0.nupkg.sha512"
},
"System.Private.ServiceModel/4.5.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ancrQgJagx+yC4SZbuE+eShiEAUIF0E1d21TRSoy1C/rTwafAVcBr/fKibkq5TQzyy9uNil2tx2/iaUxsy0S9g==",
"path": "system.private.servicemodel/4.5.3",
"hashPath": "system.private.servicemodel.4.5.3.nupkg.sha512"
},
"System.Private.Uri/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==",
"path": "system.private.uri/4.3.0",
"hashPath": "system.private.uri.4.3.0.nupkg.sha512"
},
"System.Reflection/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
"path": "system.reflection/4.3.0",
"hashPath": "system.reflection.4.3.0.nupkg.sha512"
},
"System.Reflection.DispatchProxy/4.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-+UW1hq11TNSeb+16rIk8hRQ02o339NFyzMc4ma/FqmxBzM30l1c2IherBB4ld1MNcenS48fz8tbt50OW4rVULA==",
"path": "system.reflection.dispatchproxy/4.5.0",
"hashPath": "system.reflection.dispatchproxy.4.5.0.nupkg.sha512"
},
"System.Reflection.Emit/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==",
"path": "system.reflection.emit/4.3.0",
"hashPath": "system.reflection.emit.4.3.0.nupkg.sha512"
},
"System.Reflection.Emit.ILGeneration/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
"path": "system.reflection.emit.ilgeneration/4.3.0",
"hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512"
},
"System.Reflection.Emit.Lightweight/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
"path": "system.reflection.emit.lightweight/4.3.0",
"hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512"
},
"System.Reflection.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
"path": "system.reflection.extensions/4.3.0",
"hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512"
},
"System.Reflection.Primitives/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
"path": "system.reflection.primitives/4.3.0",
"hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512"
},
"System.Reflection.TypeExtensions/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-dkmh/ySlwnXJp/1qYP9uyKkCK1CXR/REFzl7abHcArxBcV91mY2CgrrzSRA5Z/X4MevJWwXsklGRdR3A7K9zbg==",
"path": "system.reflection.typeextensions/4.4.0",
"hashPath": "system.reflection.typeextensions.4.4.0.nupkg.sha512"
},
"System.Resources.ResourceManager/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
"path": "system.resources.resourcemanager/4.3.0",
"hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512"
},
"System.Runtime/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"path": "system.runtime/4.3.0",
"hashPath": "system.runtime.4.3.0.nupkg.sha512"
},
"System.Runtime.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
"path": "system.runtime.extensions/4.3.0",
"hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512"
},
"System.Runtime.Handles/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
"path": "system.runtime.handles/4.3.0",
"hashPath": "system.runtime.handles.4.3.0.nupkg.sha512"
},
"System.Runtime.InteropServices/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
"path": "system.runtime.interopservices/4.3.0",
"hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512"
},
"System.Security.AccessControl/4.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-UPF+s4KctOCpk7dpBe3QtZ1AzFEHKdhOtiNDLxGnD/nxKsVbsow5OClTy8c7yOObw/Evbv95xRwZfYZ4wLV4fA==",
"path": "system.security.accesscontrol/4.4.0",
"hashPath": "system.security.accesscontrol.4.4.0.nupkg.sha512"
},
"System.Security.Principal.Windows/4.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-U77HfRXlZlOeIXd//Yoj6Jnk8AXlbeisf1oq1os+hxOGVnuG+lGSfGqTwTZBoORFF6j/0q7HXIl8cqwQ9aUGqQ==",
"path": "system.security.principal.windows/4.5.0",
"hashPath": "system.security.principal.windows.4.5.0.nupkg.sha512"
},
"System.ServiceModel.Primitives/4.5.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Wc9Hgg4Cmqi416zvEgq2sW1YYCGuhwWzspDclJWlFZqY6EGhFUPZU+kVpl5z9kAgrSOQP7/Uiik+PtSQtmq+5A==",
"path": "system.servicemodel.primitives/4.5.3",
"hashPath": "system.servicemodel.primitives.4.5.3.nupkg.sha512"
},
"System.Text.Encoding/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
"path": "system.text.encoding/4.3.0",
"hashPath": "system.text.encoding.4.3.0.nupkg.sha512"
},
"System.Text.Encoding.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==",
"path": "system.text.encoding.extensions/4.3.0",
"hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512"
},
"System.Text.RegularExpressions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==",
"path": "system.text.regularexpressions/4.3.0",
"hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512"
},
"System.Threading/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
"path": "system.threading/4.3.0",
"hashPath": "system.threading.4.3.0.nupkg.sha512"
},
"System.Threading.Tasks/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
"path": "system.threading.tasks/4.3.0",
"hashPath": "system.threading.tasks.4.3.0.nupkg.sha512"
},
"System.Threading.Tasks.Extensions/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==",
"path": "system.threading.tasks.extensions/4.3.0",
"hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512"
},
"System.Xml.ReaderWriter/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==",
"path": "system.xml.readerwriter/4.3.0",
"hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512"
},
"System.Xml.XmlDocument/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==",
"path": "system.xml.xmldocument/4.3.0",
"hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512"
},
"System.Xml.XmlSerializer/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==",
"path": "system.xml.xmlserializer/4.3.0",
"hashPath": "system.xml.xmlserializer.4.3.0.nupkg.sha512"
},
"SteamKit2/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}

Binary file not shown.

View File

@@ -1,10 +0,0 @@
{
"runtimeOptions": {
"tfm": "netcoreapp2.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "2.0.0"
},
"rollForwardOnNoCandidateFx": 2
}
}

View File

@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DepotDownloader", "DepotDownloader\DepotDownloader.csproj", "{39159C47-ACD3-449F-96CA-4F30C8ED147A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{39159C47-ACD3-449F-96CA-4F30C8ED147A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39159C47-ACD3-449F-96CA-4F30C8ED147A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39159C47-ACD3-449F-96CA-4F30C8ED147A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39159C47-ACD3-449F-96CA-4F30C8ED147A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using ProtoBuf;
using System.IO;
using System.IO.Compression;
using System.IO.IsolatedStorage;
using System.Linq;
using SteamKit2;
using SteamKit2.Discovery;
namespace DepotDownloader
{
[ProtoContract]
class AccountSettingsStore
{
[ProtoMember(1, IsRequired=false)]
public Dictionary<string, byte[]> SentryData { get; private set; }
[ProtoMember(2, IsRequired = false)]
public System.Collections.Concurrent.ConcurrentDictionary<string, int> ContentServerPenalty { get; private set; }
[ProtoMember(3, IsRequired = false)]
public Dictionary<string, string> LoginKeys { get; private set; }
string FileName = null;
AccountSettingsStore()
{
SentryData = new Dictionary<string, byte[]>();
ContentServerPenalty = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
LoginKeys = new Dictionary<string, string>();
}
static bool Loaded
{
get { return Instance != null; }
}
public static AccountSettingsStore Instance = null;
static readonly IsolatedStorageFile IsolatedStorage = IsolatedStorageFile.GetUserStoreForAssembly();
public static void LoadFromFile(string filename)
{
if (Loaded)
throw new Exception("Config already loaded");
if (IsolatedStorage.FileExists(filename))
{
try
{
using (var fs = IsolatedStorage.OpenFile(filename, FileMode.Open, FileAccess.Read))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress))
{
Instance = ProtoBuf.Serializer.Deserialize<AccountSettingsStore>(ds);
}
}
catch (IOException ex)
{
Console.WriteLine("Failed to load account settings: {0}", ex.Message);
Instance = new AccountSettingsStore();
}
}
else
{
Instance = new AccountSettingsStore();
}
Instance.FileName = filename;
}
public static void Save()
{
if (!Loaded)
throw new Exception("Saved config before loading");
try
{
using (var fs = IsolatedStorage.OpenFile(Instance.FileName, FileMode.Create, FileAccess.Write))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress))
{
ProtoBuf.Serializer.Serialize<AccountSettingsStore>(ds, Instance);
}
}
catch (IOException ex)
{
Console.WriteLine("Failed to save account settings: {0}", ex.Message);
}
}
}
}

View File

@@ -0,0 +1,196 @@
using SteamKit2;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace DepotDownloader
{
/// <summary>
/// CDNClientPool provides a pool of connections to CDN endpoints, requesting CDN tokens as needed
/// </summary>
class CDNClientPool
{
private const int ServerEndpointMinimumSize = 8;
private readonly Steam3Session steamSession;
private readonly uint appId;
public CDNClient CDNClient { get; }
#if STEAMKIT_UNRELEASED
public CDNClient.Server ProxyServer { get; private set; }
#endif
private readonly ConcurrentStack<CDNClient.Server> activeConnectionPool;
private readonly BlockingCollection<CDNClient.Server> availableServerEndpoints;
private readonly AutoResetEvent populatePoolEvent;
private readonly Task monitorTask;
private readonly CancellationTokenSource shutdownToken;
public CancellationTokenSource ExhaustedToken { get; set; }
public CDNClientPool(Steam3Session steamSession, uint appId)
{
this.steamSession = steamSession;
this.appId = appId;
CDNClient = new CDNClient(steamSession.steamClient);
activeConnectionPool = new ConcurrentStack<CDNClient.Server>();
availableServerEndpoints = new BlockingCollection<CDNClient.Server>();
populatePoolEvent = new AutoResetEvent(true);
shutdownToken = new CancellationTokenSource();
monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync).Unwrap();
}
public void Shutdown()
{
shutdownToken.Cancel();
monitorTask.Wait();
}
private async Task<IReadOnlyCollection<CDNClient.Server>> FetchBootstrapServerListAsync()
{
var backoffDelay = 0;
while (!shutdownToken.IsCancellationRequested)
{
try
{
var cdnServers = await ContentServerDirectoryService.LoadAsync(this.steamSession.steamClient.Configuration, ContentDownloader.Config.CellID, shutdownToken.Token);
if (cdnServers != null)
{
return cdnServers;
}
}
catch (Exception ex)
{
Console.WriteLine("Failed to retrieve content server list: {0}", ex.Message);
if (ex is SteamKitWebRequestException e && e.StatusCode == (HttpStatusCode)429)
{
// If we're being throttled, add a delay to the next request
backoffDelay = Math.Min(5, ++backoffDelay);
await Task.Delay(TimeSpan.FromSeconds(backoffDelay));
}
}
}
return null;
}
private async Task ConnectionPoolMonitorAsync()
{
bool 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;
}
#if STEAMKIT_UNRELEASED
ProxyServer = servers.Where(x => x.UseAsProxy).FirstOrDefault();
#endif
var weightedCdnServers = servers
.Where(server =>
{
#if STEAMKIT_UNRELEASED
var isEligibleForApp = server.AllowedAppIds == null || server.AllowedAppIds.Contains(appId);
return isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN");
#else
return server.Type == "SteamCache" || server.Type == "CDN";
#endif
})
.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;
}
}
}
private CDNClient.Server BuildConnection(CancellationToken token)
{
if (availableServerEndpoints.Count < ServerEndpointMinimumSize)
{
populatePoolEvent.Set();
}
return availableServerEndpoints.Take(token);
}
public CDNClient.Server GetConnection(CancellationToken token)
{
if (!activeConnectionPool.TryPop(out var connection))
{
connection = BuildConnection(token);
}
return connection;
}
public async Task<string> AuthenticateConnection(uint appId, uint depotId, CDNClient.Server server)
{
var host = steamSession.ResolveCDNTopLevelHost(server.Host);
var cdnKey = $"{depotId:D}:{host}";
steamSession.RequestCDNAuthToken(appId, depotId, host, cdnKey);
if (steamSession.CDNAuthTokens.TryGetValue(cdnKey, out var authTokenCallbackPromise))
{
var result = await authTokenCallbackPromise.Task;
return result.Token;
}
else
{
throw new Exception($"Failed to retrieve CDN token for server {server.Host} depot {depotId}");
}
}
public void ReturnConnection(CDNClient.Server server)
{
if (server == null) return;
activeConnectionPool.Push(server);
}
public void ReturnBrokenConnection(CDNClient.Server server)
{
if (server == null) return;
// Broken connections are not returned to the pool
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using ProtoBuf;
using System.IO;
using System.IO.Compression;
namespace DepotDownloader
{
[ProtoContract]
class DepotConfigStore
{
[ProtoMember(1)]
public Dictionary<uint, ulong> InstalledManifestIDs { get; private set; }
string FileName = null;
DepotConfigStore()
{
InstalledManifestIDs = new Dictionary<uint, ulong>();
}
static bool Loaded
{
get { return Instance != null; }
}
public static DepotConfigStore Instance = null;
public static void LoadFromFile(string filename)
{
if (Loaded)
throw new Exception("Config already loaded");
if (File.Exists(filename))
{
using (FileStream fs = File.Open(filename, FileMode.Open))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress))
Instance = ProtoBuf.Serializer.Deserialize<DepotConfigStore>(ds);
}
else
{
Instance = new DepotConfigStore();
}
Instance.FileName = filename;
}
public static void Save()
{
if (!Loaded)
throw new Exception("Saved config before loading");
using (FileStream fs = File.Open(Instance.FileName, FileMode.Create))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress))
ProtoBuf.Serializer.Serialize<DepotConfigStore>(ds, Instance);
}
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.0.73" />
<PackageReference Include="SteamKit2" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace DepotDownloader
{
class DownloadConfig
{
public int CellID { get; set; }
public bool DownloadAllPlatforms { get; set; }
public bool DownloadAllLanguages { get; set; }
public bool DownloadManifestOnly { get; set; }
public string InstallDirectory { get; set; }
public bool UsingFileList { get; set; }
public List<string> FilesToDownload { get; set; }
public List<Regex> FilesToDownloadRegex { get; set; }
public bool UsingExclusionList { get; set; }
public string BetaPassword { get; set; }
public bool VerifyAll { get; set; }
public int MaxServers { get; set; }
public int MaxDownloads { get; set; }
public string SuppliedPassword { get; set; }
public bool RememberPassword { get; set; }
// A Steam LoginID to allow multiple concurrent connections
public uint? LoginID {get; set; }
}
}

View File

@@ -0,0 +1,416 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using SteamKit2;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Linq;
namespace DepotDownloader
{
class Program
{
static int Main( string[] args )
=> MainAsync( args ).GetAwaiter().GetResult();
static async Task<int> MainAsync( string[] args )
{
if ( args.Length == 0 )
{
PrintUsage();
return 1;
}
DebugLog.Enabled = false;
AccountSettingsStore.LoadFromFile( "account.config" );
#region Common Options
if ( HasParameter( args, "-debug" ) )
{
DebugLog.Enabled = true;
DebugLog.AddListener( ( category, message ) =>
{
Console.WriteLine( "[{0}] {1}", category, message );
});
}
string username = GetParameter<string>( args, "-username" ) ?? GetParameter<string>( args, "-user" );
string password = GetParameter<string>( args, "-password" ) ?? GetParameter<string>( args, "-pass" );
ContentDownloader.Config.RememberPassword = HasParameter( args, "-remember-password" );
ContentDownloader.Config.DownloadManifestOnly = HasParameter( args, "-manifest-only" );
int cellId = GetParameter<int>( args, "-cellid", -1 );
if ( cellId == -1 )
{
cellId = 0;
}
ContentDownloader.Config.CellID = cellId;
string fileList = GetParameter<string>( args, "-filelist" );
string[] files = null;
if ( fileList != null )
{
try
{
string fileListData = File.ReadAllText(fileList);
files = fileListData.Split( new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries );
ContentDownloader.Config.UsingFileList = true;
ContentDownloader.Config.FilesToDownload = new List<string>();
ContentDownloader.Config.FilesToDownloadRegex = new List<Regex>();
var isWindows = RuntimeInformation.IsOSPlatform( OSPlatform.Windows );
foreach ( var fileEntry in files )
{
try
{
string fileEntryProcessed;
if ( isWindows )
{
// On Windows, ensure that forward slashes can match either forward or backslashes in depot paths
fileEntryProcessed = fileEntry.Replace( "/", "[\\\\|/]" );
}
else
{
// On other systems, treat / normally
fileEntryProcessed = fileEntry;
}
Regex rgx = new Regex( fileEntryProcessed, RegexOptions.Compiled | RegexOptions.IgnoreCase );
ContentDownloader.Config.FilesToDownloadRegex.Add( rgx );
}
catch
{
// For anything that can't be processed as a Regex, allow both forward and backward slashes to match
// on Windows
if( isWindows )
{
ContentDownloader.Config.FilesToDownload.Add( fileEntry.Replace( "/", "\\" ) );
}
ContentDownloader.Config.FilesToDownload.Add( fileEntry );
continue;
}
}
Console.WriteLine( "Using filelist: '{0}'.", fileList );
}
catch (Exception ex)
{
Console.WriteLine( "Warning: Unable to load filelist: {0}", ex.ToString() );
}
}
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<int>( args, "-max-servers", 20 );
ContentDownloader.Config.MaxDownloads = GetParameter<int>( args, "-max-downloads", 8 );
ContentDownloader.Config.MaxServers = Math.Max( ContentDownloader.Config.MaxServers, ContentDownloader.Config.MaxDownloads );
ContentDownloader.Config.LoginID = HasParameter( args, "-loginid" ) ? (uint?)GetParameter<uint>( args, "-loginid" ) : null;
#endregion
uint appId = GetParameter<uint>( args, "-app", ContentDownloader.INVALID_APP_ID );
if ( appId == ContentDownloader.INVALID_APP_ID )
{
Console.WriteLine( "Error: -app not specified!" );
return 1;
}
ulong pubFile = GetParameter<ulong>( args, "-pubfile", ContentDownloader.INVALID_MANIFEST_ID );
ulong ugcId = GetParameter<ulong>( args, "-ugc", ContentDownloader.INVALID_MANIFEST_ID );
if ( pubFile != ContentDownloader.INVALID_MANIFEST_ID )
{
#region Pubfile Downloading
if ( InitializeSteam( username, password ) )
{
try
{
await ContentDownloader.DownloadPubfileAsync( appId, pubFile ).ConfigureAwait( false );
}
catch ( Exception ex ) when (
ex is ContentDownloaderException
|| ex is OperationCanceledException )
{
Console.WriteLine( ex.Message );
return 1;
}
catch ( Exception e )
{
Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message );
throw;
}
finally
{
ContentDownloader.ShutdownSteam3();
}
}
else
{
Console.WriteLine( "Error: InitializeSteam failed" );
return 1;
}
#endregion
}
else if ( ugcId != ContentDownloader.INVALID_MANIFEST_ID )
{
#region UGC Downloading
if ( InitializeSteam( username, password ) )
{
try
{
await ContentDownloader.DownloadUGCAsync( appId, ugcId ).ConfigureAwait( false );
}
catch ( Exception ex ) when (
ex is ContentDownloaderException
|| ex is OperationCanceledException )
{
Console.WriteLine( ex.Message );
return 1;
}
catch ( Exception e )
{
Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message );
throw;
}
finally
{
ContentDownloader.ShutdownSteam3();
}
}
else
{
Console.WriteLine( "Error: InitializeSteam failed" );
return 1;
}
#endregion
}
else
{
#region App downloading
string branch = GetParameter<string>( args, "-branch" ) ?? GetParameter<string>( args, "-beta" ) ?? ContentDownloader.DEFAULT_BRANCH;
ContentDownloader.Config.BetaPassword = GetParameter<string>( args, "-betapassword" );
ContentDownloader.Config.DownloadAllPlatforms = HasParameter( args, "-all-platforms" );
string os = GetParameter<string>( args, "-os", null );
if ( ContentDownloader.Config.DownloadAllPlatforms && !String.IsNullOrEmpty( os ) )
{
Console.WriteLine("Error: Cannot specify -os when -all-platforms is specified.");
return 1;
}
string arch = GetParameter<string>( args, "-osarch", null );
ContentDownloader.Config.DownloadAllLanguages = HasParameter( args, "-all-languages" );
string language = GetParameter<string>( args, "-language", null );
if ( ContentDownloader.Config.DownloadAllLanguages && !String.IsNullOrEmpty( language ) )
{
Console.WriteLine( "Error: Cannot specify -language when -all-languages is specified." );
return 1;
}
bool lv = HasParameter( args, "-lowviolence" );
List<(uint, ulong)> depotManifestIds = new List<(uint, ulong)>();
bool isUGC = false;
List<uint> depotIdList = GetParameterList<uint>( args, "-depot" );
List<ulong> manifestIdList = GetParameterList<ulong>( args, "-manifest" );
if ( manifestIdList.Count > 0 )
{
if ( depotIdList.Count != manifestIdList.Count )
{
Console.WriteLine( "Error: -manifest requires one id for every -depot specified" );
return 1;
}
var zippedDepotManifest = depotIdList.Zip( manifestIdList, ( depotId, manifestId ) => ( depotId, manifestId ) );
depotManifestIds.AddRange( zippedDepotManifest );
}
else
{
depotManifestIds.AddRange( depotIdList.Select( depotId => ( depotId, ContentDownloader.INVALID_MANIFEST_ID ) ) );
}
if ( InitializeSteam( username, password ) )
{
try
{
await ContentDownloader.DownloadAppAsync( appId, depotManifestIds, branch, os, arch, language, lv, isUGC ).ConfigureAwait( false );
}
catch ( Exception ex ) when (
ex is ContentDownloaderException
|| ex is OperationCanceledException )
{
Console.WriteLine( ex.Message );
return 1;
}
catch ( Exception e )
{
Console.WriteLine( "Download failed to due to an unhandled exception: {0}", e.Message );
throw;
}
finally
{
ContentDownloader.ShutdownSteam3();
}
}
else
{
Console.WriteLine( "Error: InitializeSteam failed" );
return 1;
}
#endregion
}
return 0;
}
static bool InitializeSteam( string username, string password )
{
if ( username != null && password == null && ( !ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginKeys.ContainsKey( username ) ) )
{
do
{
Console.Write( "Enter account password for \"{0}\": ", username );
if ( Console.IsInputRedirected )
{
password = Console.ReadLine();
}
else
{
// Avoid console echoing of password
password = Util.ReadPassword();
}
Console.WriteLine();
} while ( String.Empty == password );
}
else if ( username == null )
{
Console.WriteLine( "No username given. Using anonymous account with dedicated server subscription." );
}
// capture the supplied password in case we need to re-use it after checking the login key
ContentDownloader.Config.SuppliedPassword = password;
return ContentDownloader.InitializeSteam3( username, password );
}
static int IndexOfParam( string[] args, string param )
{
for ( int x = 0; x < args.Length; ++x )
{
if ( args[ x ].Equals( param, StringComparison.OrdinalIgnoreCase ) )
return x;
}
return -1;
}
static bool HasParameter( string[] args, string param )
{
return IndexOfParam( args, param ) > -1;
}
static T GetParameter<T>( string[] args, string param, T defaultValue = default( T ) )
{
int index = IndexOfParam( args, param );
if ( index == -1 || index == ( args.Length - 1 ) )
return defaultValue;
string strParam = args[ index + 1 ];
var converter = TypeDescriptor.GetConverter( typeof( T ) );
if ( converter != null )
{
return ( T )converter.ConvertFromString( strParam );
}
return default( T );
}
static List<T> GetParameterList<T>(string[] args, string param)
{
List<T> list = new List<T>();
int index = IndexOfParam(args, param);
if (index == -1 || index == (args.Length - 1))
return list;
index++;
while (index < args.Length)
{
string strParam = args[index];
if (strParam[0] == '-') break;
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
list.Add((T)converter.ConvertFromString(strParam));
}
index++;
}
return list;
}
static void PrintUsage()
{
Console.WriteLine();
Console.WriteLine( "Usage - downloading one or all depots for an app:" );
Console.WriteLine( "\tdepotdownloader -app <id> [-depot <id> [-manifest <id>]]" );
Console.WriteLine( "\t\t[-username <username> [-password <password>]] [other options]" );
Console.WriteLine();
Console.WriteLine("Usage - downloading a workshop item using pubfile id");
Console.WriteLine( "\tdepotdownloader -app <id> -pubfile <id> [-username <username> [-password <password>]]" );
Console.WriteLine("Usage - downloading a workshop item using ugc id");
Console.WriteLine("\tdepotdownloader -app <id> -ugc <id> [-username <username> [-password <password>]]");
Console.WriteLine();
Console.WriteLine( "Parameters:" );
Console.WriteLine( "\t-app <#>\t\t\t\t- the AppID to download." );
Console.WriteLine( "\t-depot <#>\t\t\t\t- the DepotID to download." );
Console.WriteLine( "\t-manifest <id>\t\t\t- manifest id of content to download (requires -depot, default: current for branch)." );
Console.WriteLine( "\t-beta <branchname>\t\t\t- download from specified branch if available (default: Public)." );
Console.WriteLine( "\t-betapassword <pass>\t\t- branch password if applicable." );
Console.WriteLine( "\t-all-platforms\t\t\t- downloads all platform-specific depots when -app is used." );
Console.WriteLine( "\t-os <os>\t\t\t\t- the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)" );
Console.WriteLine( "\t-osarch <arch>\t\t\t\t- the architecture for which to download the game (32 or 64, default: the host's architecture)" );
Console.WriteLine( "\t-all-languages\t\t\t\t- download all language-specific depots when -app is used." );
Console.WriteLine( "\t-language <lang>\t\t\t\t- the language for which to download the game (default: english)" );
Console.WriteLine( "\t-lowviolence\t\t\t\t- download low violence depots when -app is used." );
Console.WriteLine();
Console.WriteLine( "\t-ugc <#>\t\t\t\t- the UGC ID to download." );
Console.WriteLine( "\t-pubfile <#>\t\t\t- the PublishedFileId to download. (Will automatically resolve to UGC id)" );
Console.WriteLine();
Console.WriteLine( "\t-username <user>\t\t- the username of the account to login to for restricted content.");
Console.WriteLine( "\t-password <pass>\t\t- the password of the account to login to for restricted content." );
Console.WriteLine( "\t-remember-password\t\t- if set, remember the password for subsequent logins of this user." );
Console.WriteLine();
Console.WriteLine( "\t-dir <installdir>\t\t- the directory in which to place downloaded files." );
Console.WriteLine( "\t-filelist <file.txt>\t- a list of files to download (from the manifest). Can optionally use regex to download only certain files." );
Console.WriteLine( "\t-validate\t\t\t\t- Include checksum verification of files already downloaded" );
Console.WriteLine();
Console.WriteLine( "\t-manifest-only\t\t\t- downloads a human readable manifest for any depots that would be downloaded." );
Console.WriteLine( "\t-cellid <#>\t\t\t\t- the overridden CellID of the content server to download from." );
Console.WriteLine( "\t-max-servers <#>\t\t- maximum number of content servers to use. (default: 20)." );
Console.WriteLine( "\t-max-downloads <#>\t\t- maximum number of chunks to download concurrently. (default: 8)." );
Console.WriteLine( "\t-loginid <#>\t\t- a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently." );
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "DepotDownloader" )]
[assembly: AssemblyDescription("Steam Downloading Utility")]
[assembly: AssemblyConfiguration( "" )]
[assembly: AssemblyCompany("SteamRE Team")]
[assembly: AssemblyProduct( "DepotDownloader" )]
[assembly: AssemblyCopyright("Copyright © SteamRE Team 2017")]
[assembly: AssemblyTrademark( "" )]
[assembly: AssemblyCulture( "" )]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid( "df2ab32a-923c-46e3-a1b4-c901ee92ec94" )]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.3.0.0")]

View File

@@ -0,0 +1,7 @@
{
"profiles": {
"DepotDownloader": {
"commandName": "Project"
}
}
}

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using ProtoBuf;
using SteamKit2;
namespace DepotDownloader
{
[ProtoContract()]
class ProtoManifest
{
// Proto ctor
private ProtoManifest()
{
Files = new List<FileData>();
}
public ProtoManifest(DepotManifest sourceManifest, ulong id) : this()
{
sourceManifest.Files.ForEach(f => Files.Add(new FileData(f)));
ID = id;
CreationTime = sourceManifest.CreationTime;
}
[ProtoContract()]
public class FileData
{
// Proto ctor
private FileData()
{
Chunks = new List<ChunkData>();
}
public FileData(DepotManifest.FileData sourceData) : this()
{
FileName = sourceData.FileName;
sourceData.Chunks.ForEach(c => Chunks.Add(new ChunkData(c)));
Flags = sourceData.Flags;
TotalSize = sourceData.TotalSize;
FileHash = sourceData.FileHash;
}
[ProtoMember(1)]
public string FileName { get; internal set; }
/// <summary>
/// Gets the chunks that this file is composed of.
/// </summary>
[ProtoMember(2)]
public List<ChunkData> Chunks { get; private set; }
/// <summary>
/// Gets the file flags
/// </summary>
[ProtoMember(3)]
public EDepotFileFlag Flags { get; private set; }
/// <summary>
/// Gets the total size of this file.
/// </summary>
[ProtoMember(4)]
public ulong TotalSize { get; private set; }
/// <summary>
/// Gets the hash of this file.
/// </summary>
[ProtoMember(5)]
public byte[] FileHash { get; private set; }
}
[ProtoContract(SkipConstructor = true)]
public class ChunkData
{
public ChunkData(DepotManifest.ChunkData sourceChunk)
{
ChunkID = sourceChunk.ChunkID;
Checksum = sourceChunk.Checksum;
Offset = sourceChunk.Offset;
CompressedLength = sourceChunk.CompressedLength;
UncompressedLength = sourceChunk.UncompressedLength;
}
/// <summary>
/// Gets the SHA-1 hash chunk id.
/// </summary>
[ProtoMember(1)]
public byte[] ChunkID { get; private set; }
/// <summary>
/// Gets the expected Adler32 checksum of this chunk.
/// </summary>
[ProtoMember(2)]
public byte[] Checksum { get; private set; }
/// <summary>
/// Gets the chunk offset.
/// </summary>
[ProtoMember(3)]
public ulong Offset { get; private set; }
/// <summary>
/// Gets the compressed length of this chunk.
/// </summary>
[ProtoMember(4)]
public uint CompressedLength { get; private set; }
/// <summary>
/// Gets the decompressed length of this chunk.
/// </summary>
[ProtoMember(5)]
public uint UncompressedLength { get; private set; }
}
[ProtoMember(1)]
public List<FileData> Files { get; private set; }
[ProtoMember(2)]
public ulong ID { get; private set; }
[ProtoMember(3)]
public DateTime CreationTime { get; private set; }
public static ProtoManifest LoadFromFile(string filename, out byte[] checksum)
{
if (!File.Exists(filename))
{
checksum = null;
return null;
}
using (MemoryStream ms = new MemoryStream())
{
using (FileStream fs = File.Open(filename, FileMode.Open))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress))
ds.CopyTo(ms);
checksum = Util.SHAHash(ms.ToArray());
ms.Seek(0, SeekOrigin.Begin);
return ProtoBuf.Serializer.Deserialize<ProtoManifest>(ms);
}
}
public void SaveToFile(string filename, out byte[] checksum)
{
using (MemoryStream ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize<ProtoManifest>(ms, this);
checksum = Util.SHAHash(ms.ToArray());
ms.Seek(0, SeekOrigin.Begin);
using (FileStream fs = File.Open(filename, FileMode.Create))
using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress))
ms.CopyTo(ds);
}
}
}
}

View File

@@ -0,0 +1,771 @@
using SteamKit2;
using SteamKit2.Internal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace DepotDownloader
{
class Steam3Session
{
public class Credentials
{
public bool LoggedOn { get; set; }
public ulong SessionToken { get; set; }
public bool IsValid
{
get { return LoggedOn; }
}
}
public ReadOnlyCollection<SteamApps.LicenseListCallback.License> Licenses
{
get;
private set;
}
public Dictionary<uint, byte[]> AppTickets { get; private set; }
public Dictionary<uint, ulong> AppTokens { get; private set; }
public Dictionary<uint, ulong> PackageTokens { get; private set; }
public Dictionary<uint, byte[]> DepotKeys { get; private set; }
public ConcurrentDictionary<string, TaskCompletionSource<SteamApps.CDNAuthTokenCallback>> CDNAuthTokens { get; private set; }
public Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo> AppInfo { get; private set; }
public Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo> PackageInfo { get; private set; }
public Dictionary<string, byte[]> AppBetaPasswords { get; private set; }
public SteamClient steamClient;
public SteamUser steamUser;
SteamApps steamApps;
SteamCloud steamCloud;
SteamUnifiedMessages.UnifiedService<IPublishedFile> steamPublishedFile;
CallbackManager callbacks;
bool authenticatedUser;
bool bConnected;
bool bConnecting;
bool bAborted;
bool bExpectingDisconnectRemote;
bool bDidDisconnect;
bool bDidReceiveLoginKey;
int connectionBackoff;
int seq; // more hack fixes
DateTime connectTime;
// input
SteamUser.LogOnDetails logonDetails;
// output
Credentials credentials;
static readonly TimeSpan STEAM3_TIMEOUT = TimeSpan.FromSeconds( 30 );
public Steam3Session( SteamUser.LogOnDetails details )
{
this.logonDetails = details;
this.authenticatedUser = details.Username != null;
this.credentials = new Credentials();
this.bConnected = false;
this.bConnecting = false;
this.bAborted = false;
this.bExpectingDisconnectRemote = false;
this.bDidDisconnect = false;
this.bDidReceiveLoginKey = false;
this.seq = 0;
this.AppTickets = new Dictionary<uint, byte[]>();
this.AppTokens = new Dictionary<uint, ulong>();
this.PackageTokens = new Dictionary<uint, ulong>();
this.DepotKeys = new Dictionary<uint, byte[]>();
this.CDNAuthTokens = new ConcurrentDictionary<string, TaskCompletionSource<SteamApps.CDNAuthTokenCallback>>();
this.AppInfo = new Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo>();
this.PackageInfo = new Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo>();
this.AppBetaPasswords = new Dictionary<string, byte[]>();
this.steamClient = new SteamClient();
this.steamUser = this.steamClient.GetHandler<SteamUser>();
this.steamApps = this.steamClient.GetHandler<SteamApps>();
this.steamCloud = this.steamClient.GetHandler<SteamCloud>();
var steamUnifiedMessages = this.steamClient.GetHandler<SteamUnifiedMessages>();
this.steamPublishedFile = steamUnifiedMessages.CreateService<IPublishedFile>();
this.callbacks = new CallbackManager( this.steamClient );
this.callbacks.Subscribe<SteamClient.ConnectedCallback>( ConnectedCallback );
this.callbacks.Subscribe<SteamClient.DisconnectedCallback>( DisconnectedCallback );
this.callbacks.Subscribe<SteamUser.LoggedOnCallback>( LogOnCallback );
this.callbacks.Subscribe<SteamUser.SessionTokenCallback>( SessionTokenCallback );
this.callbacks.Subscribe<SteamApps.LicenseListCallback>( LicenseListCallback );
this.callbacks.Subscribe<SteamUser.UpdateMachineAuthCallback>( UpdateMachineAuthCallback );
this.callbacks.Subscribe<SteamUser.LoginKeyCallback>( LoginKeyCallback );
Console.Write( "Connecting to Steam3..." );
if ( authenticatedUser )
{
FileInfo fi = new FileInfo( String.Format( "{0}.sentryFile", logonDetails.Username ) );
if ( AccountSettingsStore.Instance.SentryData != null && AccountSettingsStore.Instance.SentryData.ContainsKey( logonDetails.Username ) )
{
logonDetails.SentryFileHash = Util.SHAHash( AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] );
}
else if ( fi.Exists && fi.Length > 0 )
{
var sentryData = File.ReadAllBytes( fi.FullName );
logonDetails.SentryFileHash = Util.SHAHash( sentryData );
AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] = sentryData;
AccountSettingsStore.Save();
}
}
Connect();
}
public delegate bool WaitCondition();
private object steamLock = new object();
public bool WaitUntilCallback( Action submitter, WaitCondition waiter )
{
while ( !bAborted && !waiter() )
{
lock (steamLock)
{
submitter();
}
int seq = this.seq;
do
{
lock (steamLock)
{
WaitForCallbacks();
}
}
while ( !bAborted && this.seq == seq && !waiter() );
}
return bAborted;
}
public Credentials WaitForCredentials()
{
if ( credentials.IsValid || bAborted )
return credentials;
WaitUntilCallback( () => { }, () => { return credentials.IsValid; } );
return credentials;
}
public void RequestAppInfo( uint appId, bool bForce = false )
{
if ( ( AppInfo.ContainsKey( appId ) && !bForce ) || bAborted )
return;
bool completed = false;
Action<SteamApps.PICSTokensCallback> cbMethodTokens = ( appTokens ) =>
{
completed = true;
if ( appTokens.AppTokensDenied.Contains( 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( () =>
{
callbacks.Subscribe( steamApps.PICSGetAccessTokens( new List<uint>() { appId }, new List<uint>() { } ), cbMethodTokens );
}, () => { return completed; } );
completed = false;
Action<SteamApps.PICSProductInfoCallback> cbMethod = ( appInfo ) =>
{
completed = !appInfo.ResponsePending;
foreach ( var app_value in appInfo.Apps )
{
var app = app_value.Value;
Console.WriteLine( "Got AppInfo for {0}", app.ID );
AppInfo[ app.ID ] = app;
}
foreach ( var app in appInfo.UnknownApps )
{
AppInfo[ app ] = null;
}
};
SteamApps.PICSRequest request = new SteamApps.PICSRequest( appId );
if ( AppTokens.ContainsKey( appId ) )
{
request.AccessToken = AppTokens[ appId ];
request.Public = false;
}
WaitUntilCallback( () =>
{
callbacks.Subscribe( steamApps.PICSGetProductInfo( new List<SteamApps.PICSRequest>() { request }, new List<SteamApps.PICSRequest>() { } ), cbMethod );
}, () => { return completed; } );
}
public void RequestPackageInfo( IEnumerable<uint> packageIds )
{
List<uint> packages = packageIds.ToList();
packages.RemoveAll( pid => PackageInfo.ContainsKey( pid ) );
if ( packages.Count == 0 || bAborted )
return;
bool 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 )
{
var request = new SteamApps.PICSRequest( package );
if ( PackageTokens.TryGetValue( package, out var token ) )
{
request.AccessToken = token;
request.Public = false;
}
packageRequests.Add( request );
}
WaitUntilCallback( () =>
{
callbacks.Subscribe( steamApps.PICSGetProductInfo( new List<SteamApps.PICSRequest>(), packageRequests ), cbMethod );
}, () => { return completed; } );
}
public bool RequestFreeAppLicense( uint appId )
{
bool success = false;
bool completed = false;
Action<SteamApps.FreeLicenseCallback> cbMethod = ( resultInfo ) =>
{
completed = true;
success = resultInfo.GrantedApps.Contains( appId );
};
WaitUntilCallback( () =>
{
callbacks.Subscribe( steamApps.RequestFreeLicense( appId ), cbMethod );
}, () => { return completed; } );
return success;
}
public void RequestAppTicket( uint appId )
{
if ( AppTickets.ContainsKey( appId ) || bAborted )
return;
if ( !authenticatedUser )
{
AppTickets[ appId ] = null;
return;
}
bool completed = false;
Action<SteamApps.AppOwnershipTicketCallback> cbMethod = ( appTicket ) =>
{
completed = true;
if ( appTicket.Result != EResult.OK )
{
Console.WriteLine( "Unable to get appticket for {0}: {1}", appTicket.AppID, appTicket.Result );
Abort();
}
else
{
Console.WriteLine( "Got appticket for {0}!", appTicket.AppID );
AppTickets[ appTicket.AppID ] = appTicket.Ticket;
}
};
WaitUntilCallback( () =>
{
callbacks.Subscribe( steamApps.GetAppOwnershipTicket( appId ), cbMethod );
}, () => { return completed; } );
}
public void RequestDepotKey( uint depotId, uint appid = 0 )
{
if ( DepotKeys.ContainsKey( depotId ) || bAborted )
return;
bool completed = false;
Action<SteamApps.DepotKeyCallback> cbMethod = ( depotKey ) =>
{
completed = true;
Console.WriteLine( "Got depot key for {0} result: {1}", depotKey.DepotID, depotKey.Result );
if ( depotKey.Result != EResult.OK )
{
Abort();
return;
}
DepotKeys[ depotKey.DepotID ] = depotKey.DepotKey;
};
WaitUntilCallback( () =>
{
callbacks.Subscribe( steamApps.GetDepotDecryptionKey( depotId, appid ), cbMethod );
}, () => { return completed; } );
}
public string ResolveCDNTopLevelHost(string host)
{
// SteamPipe CDN shares tokens with all hosts
if (host.EndsWith( ".steampipe.steamcontent.com" ) )
{
return "steampipe.steamcontent.com";
}
else if (host.EndsWith(".steamcontent.com"))
{
return "steamcontent.com";
}
return host;
}
public void RequestCDNAuthToken( uint appid, uint depotid, string host, string cdnKey )
{
if ( CDNAuthTokens.ContainsKey( cdnKey ) || bAborted )
return;
if ( !CDNAuthTokens.TryAdd( cdnKey, new TaskCompletionSource<SteamApps.CDNAuthTokenCallback>() ) )
return;
bool completed = false;
var timeoutDate = DateTime.Now.AddSeconds( 10 );
Action<SteamApps.CDNAuthTokenCallback> cbMethod = ( cdnAuth ) =>
{
completed = true;
Console.WriteLine( "Got CDN auth token for {0} result: {1} (expires {2})", host, cdnAuth.Result, cdnAuth.Expiration );
if ( cdnAuth.Result != EResult.OK )
{
Abort();
return;
}
CDNAuthTokens[cdnKey].TrySetResult( cdnAuth );
};
WaitUntilCallback( () =>
{
callbacks.Subscribe( steamApps.GetCDNAuthToken( appid, depotid, host ), cbMethod );
}, () => { return completed || DateTime.Now >= timeoutDate; } );
}
public void CheckAppBetaPassword( uint appid, string password )
{
bool completed = false;
Action<SteamApps.CheckAppBetaPasswordCallback> cbMethod = ( appPassword ) =>
{
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; } );
}
public CPublishedFile_GetItemInfo_Response.WorkshopItemInfo GetPubfileItemInfo( uint appId, PublishedFileID pubFile )
{
var pubFileRequest = new CPublishedFile_GetItemInfo_Request() { app_id = appId };
pubFileRequest.workshop_items.Add( new CPublishedFile_GetItemInfo_Request.WorkshopItem() { published_file_id = pubFile } );
bool completed = false;
CPublishedFile_GetItemInfo_Response.WorkshopItemInfo details = null;
Action<SteamUnifiedMessages.ServiceMethodResponse> cbMethod = callback =>
{
completed = true;
if ( callback.Result == EResult.OK )
{
var response = callback.GetDeserializedResponse<CPublishedFile_GetItemInfo_Response>();
details = response.workshop_items.FirstOrDefault();
}
else
{
throw new Exception( $"EResult {(int)callback.Result} ({callback.Result}) while retrieving UGC id for pubfile {pubFile}.");
}
};
WaitUntilCallback(() =>
{
callbacks.Subscribe( steamPublishedFile.SendMessage( api => api.GetItemInfo( pubFileRequest ) ), cbMethod );
}, () => { return completed; });
return details;
}
public PublishedFileDetails GetPublishedFileDetails(uint appId, PublishedFileID pubFile)
{
var pubFileRequest = new CPublishedFile_GetDetails_Request() { appid = appId };
pubFileRequest.publishedfileids.Add( pubFile );
bool completed = false;
PublishedFileDetails details = null;
Action<SteamUnifiedMessages.ServiceMethodResponse> cbMethod = callback =>
{
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}.");
}
};
WaitUntilCallback(() =>
{
callbacks.Subscribe(steamPublishedFile.SendMessage(api => api.GetDetails(pubFileRequest)), cbMethod);
}, () => { return completed; });
return details;
}
public SteamCloud.UGCDetailsCallback GetUGCDetails(UGCHandle ugcHandle)
{
bool completed = false;
SteamCloud.UGCDetailsCallback details = null;
Action<SteamCloud.UGCDetailsCallback> cbMethod = callback =>
{
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(() =>
{
callbacks.Subscribe(steamCloud.RequestUGCDetails(ugcHandle), cbMethod);
}, () => { return completed; });
return details;
}
void Connect()
{
bAborted = false;
bConnected = false;
bConnecting = true;
connectionBackoff = 0;
bExpectingDisconnectRemote = false;
bDidDisconnect = false;
bDidReceiveLoginKey = false;
this.connectTime = DateTime.Now;
this.steamClient.Connect();
}
private void Abort( bool sendLogOff = true )
{
Disconnect( sendLogOff );
}
public void Disconnect( bool sendLogOff = true )
{
if ( sendLogOff )
{
steamUser.LogOff();
}
steamClient.Disconnect();
bConnected = false;
bConnecting = false;
bAborted = true;
// flush callbacks until our disconnected event
while ( !bDidDisconnect )
{
callbacks.RunWaitAllCallbacks( TimeSpan.FromMilliseconds( 100 ) );
}
}
public void TryWaitForLoginKey()
{
if ( logonDetails.Username == null || !credentials.LoggedOn || !ContentDownloader.Config.RememberPassword ) return;
var totalWaitPeriod = DateTime.Now.AddSeconds( 3 );
while ( true )
{
DateTime now = DateTime.Now;
if ( now >= totalWaitPeriod ) break;
if ( bDidReceiveLoginKey ) break;
callbacks.RunWaitAllCallbacks( TimeSpan.FromMilliseconds( 100 ) );
}
}
private void WaitForCallbacks()
{
callbacks.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) );
TimeSpan diff = DateTime.Now - connectTime;
if ( diff > STEAM3_TIMEOUT && !bConnected )
{
Console.WriteLine( "Timeout connecting to Steam3." );
Abort();
return;
}
}
private void ConnectedCallback( SteamClient.ConnectedCallback connected )
{
Console.WriteLine( " Done!" );
bConnecting = false;
bConnected = true;
if ( !authenticatedUser )
{
Console.Write( "Logging anonymously into Steam3..." );
steamUser.LogOnAnonymous();
}
else
{
Console.Write( "Logging '{0}' into Steam3...", logonDetails.Username );
steamUser.LogOn( logonDetails );
}
}
private void DisconnectedCallback( SteamClient.DisconnectedCallback disconnected )
{
bDidDisconnect = true;
if ( disconnected.UserInitiated || bExpectingDisconnectRemote )
{
Console.WriteLine( "Disconnected from Steam" );
}
else if ( connectionBackoff >= 10 )
{
Console.WriteLine( "Could not connect to Steam after 10 tries" );
Abort( false );
}
else if ( !bAborted )
{
if ( bConnecting )
{
Console.WriteLine( "Connection to Steam failed. Trying again" );
}
else
{
Console.WriteLine( "Lost connection to Steam. Reconnecting" );
}
Thread.Sleep( 1000 * ++connectionBackoff );
steamClient.Connect();
}
}
private void LogOnCallback( SteamUser.LoggedOnCallback loggedOn )
{
bool isSteamGuard = loggedOn.Result == EResult.AccountLogonDenied;
bool is2FA = loggedOn.Result == EResult.AccountLoginDeniedNeedTwoFactor;
bool isLoginKey = ContentDownloader.Config.RememberPassword && logonDetails.LoginKey != null && loggedOn.Result == EResult.InvalidPassword;
if ( isSteamGuard || is2FA || isLoginKey )
{
bExpectingDisconnectRemote = true;
Abort( false );
if ( !isLoginKey )
{
Console.WriteLine( "This account is protected by Steam Guard." );
}
if ( is2FA )
{
Console.Write( "Please enter your 2 factor auth code from your authenticator app: " );
logonDetails.TwoFactorCode = Console.ReadLine();
}
else if ( isLoginKey )
{
AccountSettingsStore.Instance.LoginKeys.Remove( logonDetails.Username );
AccountSettingsStore.Save();
logonDetails.LoginKey = null;
if ( ContentDownloader.Config.SuppliedPassword != null )
{
Console.WriteLine( "Login key was expired. Connecting with supplied password." );
logonDetails.Password = ContentDownloader.Config.SuppliedPassword;
}
else
{
Console.Write( "Login key was expired. Please enter your password: " );
logonDetails.Password = Util.ReadPassword();
}
}
else
{
Console.Write( "Please enter the authentication code sent to your email address: " );
logonDetails.AuthCode = Console.ReadLine();
}
Console.Write( "Retrying Steam3 connection..." );
Connect();
return;
}
else if ( loggedOn.Result == EResult.ServiceUnavailable )
{
Console.WriteLine( "Unable to login to Steam3: {0}", loggedOn.Result );
Abort( false );
return;
}
else if ( loggedOn.Result != EResult.OK )
{
Console.WriteLine( "Unable to login to Steam3: {0}", loggedOn.Result );
Abort();
return;
}
Console.WriteLine( " Done!" );
this.seq++;
credentials.LoggedOn = true;
if ( ContentDownloader.Config.CellID == 0 )
{
Console.WriteLine( "Using Steam3 suggested CellID: " + loggedOn.CellID );
ContentDownloader.Config.CellID = ( int )loggedOn.CellID;
}
}
private void SessionTokenCallback( SteamUser.SessionTokenCallback sessionToken )
{
Console.WriteLine( "Got session token!" );
credentials.SessionToken = sessionToken.SessionToken;
}
private void LicenseListCallback( SteamApps.LicenseListCallback licenseList )
{
if ( licenseList.Result != EResult.OK )
{
Console.WriteLine( "Unable to get license list: {0} ", licenseList.Result );
Abort();
return;
}
Console.WriteLine( "Got {0} licenses for account!", licenseList.LicenseList.Count );
Licenses = licenseList.LicenseList;
foreach ( var license in licenseList.LicenseList )
{
if ( license.AccessToken > 0 )
{
PackageTokens.TryAdd( license.PackageID, license.AccessToken );
}
}
}
private void UpdateMachineAuthCallback( SteamUser.UpdateMachineAuthCallback machineAuth )
{
byte[] hash = Util.SHAHash( machineAuth.Data );
Console.WriteLine( "Got Machine Auth: {0} {1} {2} {3}", machineAuth.FileName, machineAuth.Offset, machineAuth.BytesToWrite, machineAuth.Data.Length, hash );
AccountSettingsStore.Instance.SentryData[ logonDetails.Username ] = machineAuth.Data;
AccountSettingsStore.Save();
var authResponse = new SteamUser.MachineAuthDetails
{
BytesWritten = machineAuth.BytesToWrite,
FileName = machineAuth.FileName,
FileSize = machineAuth.BytesToWrite,
Offset = machineAuth.Offset,
SentryFileHash = hash, // should be the sha1 hash of the sentry file we just wrote
OneTimePassword = machineAuth.OneTimePassword, // not sure on this one yet, since we've had no examples of steam using OTPs
LastError = 0, // result from win32 GetLastError
Result = EResult.OK, // if everything went okay, otherwise ~who knows~
JobID = machineAuth.JobID, // so we respond to the correct server job
};
// send off our response
steamUser.SendMachineAuthResponse( authResponse );
}
private void LoginKeyCallback( SteamUser.LoginKeyCallback loginKey )
{
Console.WriteLine( "Accepted new login key for account {0}", logonDetails.Username );
AccountSettingsStore.Instance.LoginKeys[ logonDetails.Username ] = loginKey.LoginKey;
AccountSettingsStore.Save();
steamUser.AcceptNewLoginKey( loginKey );
bDidReceiveLoginKey = true;
}
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DepotDownloader
{
static class Util
{
public static string GetSteamOS()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "windows";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "macos";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "linux";
}
return "unknown";
}
public static string GetSteamArch()
{
return Environment.Is64BitOperatingSystem ? "64" : "32";
}
public static string ReadPassword()
{
ConsoleKeyInfo keyInfo;
StringBuilder password = new StringBuilder();
do
{
keyInfo = Console.ReadKey( true );
if ( keyInfo.Key == ConsoleKey.Backspace )
{
if ( password.Length > 0 )
password.Remove( password.Length - 1, 1 );
continue;
}
/* Printable ASCII characters only */
char c = keyInfo.KeyChar;
if ( c >= ' ' && c <= '~' )
password.Append( c );
} while ( keyInfo.Key != ConsoleKey.Enter );
return password.ToString();
}
// Validate a file against Steam3 Chunk data
public static List<ProtoManifest.ChunkData> ValidateSteam3FileChecksums(FileStream fs, ProtoManifest.ChunkData[] chunkdata)
{
var neededChunks = new List<ProtoManifest.ChunkData>();
int read;
foreach (var data in chunkdata)
{
byte[] chunk = new byte[data.UncompressedLength];
fs.Seek((long)data.Offset, SeekOrigin.Begin);
read = fs.Read(chunk, 0, (int)data.UncompressedLength);
byte[] tempchunk;
if (read < data.UncompressedLength)
{
tempchunk = new byte[read];
Array.Copy(chunk, 0, tempchunk, 0, read);
}
else
{
tempchunk = chunk;
}
byte[] adler = AdlerHash(tempchunk);
if (!adler.SequenceEqual(data.Checksum))
{
neededChunks.Add(data);
}
}
return neededChunks;
}
public static byte[] AdlerHash(byte[] input)
{
uint a = 0, b = 0;
for (int i = 0; i < input.Length; i++)
{
a = (a + input[i]) % 65521;
b = (b + a) % 65521;
}
return BitConverter.GetBytes(a | (b << 16));
}
public static byte[] SHAHash( byte[] input )
{
using (var sha = SHA1.Create())
{
var output = sha.ComputeHash( input );
return output;
}
}
public static byte[] DecodeHexString( string hex )
{
if ( hex == null )
return null;
int chars = hex.Length;
byte[] bytes = new byte[ chars / 2 ];
for ( int i = 0 ; i < chars ; i += 2 )
bytes[ i / 2 ] = Convert.ToByte( hex.Substring( i, 2 ), 16 );
return bytes;
}
public static string EncodeHexString( byte[] input )
{
return input.Aggregate( new StringBuilder(),
( sb, v ) => sb.Append( v.ToString( "x2" ) )
).ToString();
}
public static async Task InvokeAsync(IEnumerable<Func<Task>> taskFactories, int maxDegreeOfParallelism)
{
if (taskFactories == null) throw new ArgumentNullException(nameof(taskFactories));
if (maxDegreeOfParallelism <= 0) throw new ArgumentException(nameof(maxDegreeOfParallelism));
Func<Task>[] queue = taskFactories.ToArray();
if (queue.Length == 0)
{
return;
}
List<Task> tasksInFlight = new List<Task>(maxDegreeOfParallelism);
int index = 0;
do
{
while (tasksInFlight.Count < maxDegreeOfParallelism && index < queue.Length)
{
Func<Task> taskFactory = queue[index++];
tasksInFlight.Add(taskFactory());
}
Task completedTask = await Task.WhenAny(tasksInFlight).ConfigureAwait(false);
await completedTask.ConfigureAwait(false);
tasksInFlight.Remove(completedTask);
}
while (index < queue.Length || tasksInFlight.Count != 0);
}
}
}

View File

@@ -0,0 +1,3 @@
{
"rollForwardOnNoCandidateFx": 2
}

View File

@@ -50,6 +50,6 @@ Parameter | Description
-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: 8).
-max-downloads \<#> | maximum number of chunks to download concurrently. (default: 4).
-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.

Binary file not shown.

View File

@@ -1 +0,0 @@
dotnet DepotDownloader.dll "$@"

View File

@@ -1,2 +0,0 @@
@echo off
dotnet %~dp0DepotDownloader.dll %*

Binary file not shown.