├── .gitignore ├── DBD-API.sln ├── DBD-API ├── ClientApp │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── items │ │ │ │ ├── common.png │ │ │ │ ├── events.png │ │ │ │ ├── rare.png │ │ │ │ ├── ultrarare.png │ │ │ │ ├── uncommon.png │ │ │ │ └── veryrare.png │ │ │ ├── logo.png │ │ │ ├── main.scss │ │ │ ├── offerings │ │ │ │ ├── common.png │ │ │ │ ├── rare.png │ │ │ │ ├── ultrarare.png │ │ │ │ ├── uncommon.png │ │ │ │ └── veryrare.png │ │ │ └── perks │ │ │ │ ├── common.png │ │ │ │ ├── old │ │ │ │ ├── common.png │ │ │ │ ├── rare.png │ │ │ │ ├── teachable.png │ │ │ │ ├── ultrarare.png │ │ │ │ ├── uncommon.png │ │ │ │ └── veryrare.png │ │ │ │ ├── rare.png │ │ │ │ ├── teachable.png │ │ │ │ ├── ultrarare.png │ │ │ │ ├── uncommon.png │ │ │ │ └── veryrare.png │ │ ├── components │ │ │ ├── AddonsList.vue │ │ │ ├── OfferingList.vue │ │ │ ├── PerkList.vue │ │ │ └── TunablesTable.vue │ │ ├── index.html │ │ ├── main.js │ │ ├── router.js │ │ ├── services │ │ │ └── ApiService.js │ │ └── views │ │ │ ├── CharacterInfo.vue │ │ │ ├── Characters.vue │ │ │ ├── News.vue │ │ │ ├── Offerings.vue │ │ │ ├── Perks.vue │ │ │ └── Shrine.vue │ ├── webpack.config.js │ └── yarn.lock ├── Controllers │ └── APIController.cs ├── DBD-API.csproj ├── DBD-API.csproj.user ├── Modules │ ├── DbD │ │ ├── Extensions.cs │ │ ├── Extra.cs │ │ ├── JsonResponse │ │ │ ├── Converters │ │ │ │ ├── CurrencyIdConverter.cs │ │ │ │ └── DateTimeConverter.cs │ │ │ ├── ShrineResponse.cs │ │ │ └── StoreResponse.cs │ │ └── PakItems │ │ │ ├── BaseInfo.cs │ │ │ ├── BaseItem.cs │ │ │ ├── CharacterInfo.cs │ │ │ ├── CustomItemInfo.cs │ │ │ ├── ItemAddonInfo.cs │ │ │ ├── MapInfo.cs │ │ │ ├── OfferingInfo.cs │ │ │ ├── PerkInfo.cs │ │ │ └── TunableInfo.cs │ ├── SPA │ │ └── VueHelper.cs │ └── Steam │ │ ├── ContentManagement │ │ ├── CDNClientPool.cs │ │ ├── ContentDownloader.cs │ │ └── ProtoManifest.cs │ │ ├── SteamMatchmaking.cs │ │ ├── SteamTicketAccepted.cs │ │ └── SteamUserStatsResponse.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Services │ ├── CacheService.cs │ ├── DdbService.cs │ ├── SteamDepotService.cs │ ├── SteamEventService.cs │ └── SteamService.cs ├── Startup.cs └── libman.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | sentry.bin 3 | .vs/ 4 | bin/ 5 | PublishProfiles/ 6 | obj/ 7 | DBD-API/manifest_cache/ 8 | DBD-API/data/ 9 | 10 | DBD-API.sln.DotSettings.user 11 | 12 | DBD-API/config.json 13 | -------------------------------------------------------------------------------- /DBD-API.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.28711.60 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBD-API", "DBD-API\DBD-API.csproj", "{BB41E6CF-FF88-480B-B12D-A351794B6D85}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Debug|ARM = Debug|ARM 11 | Debug|ARM64 = Debug|ARM64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|ARM = Release|ARM 15 | Release|ARM64 = Release|ARM64 16 | Release|x86 = Release|x86 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|ARM.ActiveCfg = Debug|Any CPU 22 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|ARM.Build.0 = Debug|Any CPU 23 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|ARM64.ActiveCfg = Debug|Any CPU 24 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|ARM64.Build.0 = Debug|Any CPU 25 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|x86.ActiveCfg = Debug|x86 26 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Debug|x86.Build.0 = Debug|x86 27 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|ARM.ActiveCfg = Release|Any CPU 30 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|ARM.Build.0 = Release|Any CPU 31 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|ARM64.ActiveCfg = Release|Any CPU 32 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|ARM64.Build.0 = Release|Any CPU 33 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|x86.ActiveCfg = Release|x86 34 | {BB41E6CF-FF88-480B-B12D-A351794B6D85}.Release|x86.Build.0 = Release|x86 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {5DF68AF7-ABA4-4100-9340-3E170E73E7C3} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "stage-2", 4 | ["env", { "modules": false }] 5 | ] 6 | } 7 | 8 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # client-app 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 7 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 8 | }, 9 | "dependencies": { 10 | "@fortawesome/fontawesome-svg-core": "^1.2.26", 11 | "@fortawesome/free-brands-svg-icons": "^5.12.0", 12 | "@fortawesome/pro-light-svg-icons": "^5.12.0", 13 | "@fortawesome/pro-regular-svg-icons": "^5.12.0", 14 | "@fortawesome/pro-solid-svg-icons": "^5.12.0", 15 | "@fortawesome/vue-fontawesome": "^0.1.9", 16 | "@vue/compiler-sfc": "^3.0.0-alpha.4", 17 | "ant-design-vue": "^1.4.10", 18 | "babel-plugin-import": "^1.13.0", 19 | "core-js": "^3.4.4", 20 | "favicons-webpack-plugin": "^2.1.0", 21 | "moment-timezone": "^0.5.27", 22 | "resolve-url-loader": "^3.1.1", 23 | "vue": "^2.6.11", 24 | "vue-loader": "^15.7.0", 25 | "vue-moment": "^4.1.0", 26 | "vue-router": "^3.1.5", 27 | "webpack-cli": "^3.3.10" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^7.1.1", 31 | "babel-core": "^6.25.0", 32 | "babel-loader": "^7.1.1", 33 | "babel-preset-env": "^1.5.2", 34 | "babel-preset-stage-2": "^6.24.1", 35 | "cross-env": "^5.0.1", 36 | "css-loader": "^3.4.2", 37 | "file-loader": "^5.0.2", 38 | "html-webpack-plugin": "^3.2.0", 39 | "node-sass": "^4.13.1", 40 | "postcss-loader": "^2.0.6", 41 | "rimraf": "^2.6.1", 42 | "sass-loader": "^8.0.2", 43 | "style-loader": "^0.18.2", 44 | "url-loader": "^3.0.0", 45 | "vue-template-compiler": "^2.6.11", 46 | "webpack": "^4.41.5", 47 | "webpack-dev-server": "^3.10.2" 48 | }, 49 | "eslintConfig": { 50 | "root": true, 51 | "env": { 52 | "node": true 53 | }, 54 | "extends": [ 55 | "plugin:vue/essential", 56 | "eslint:recommended" 57 | ], 58 | "rules": {}, 59 | "parserOptions": { 60 | "parser": "babel-eslint" 61 | } 62 | }, 63 | "browserslist": [ 64 | "> 1%", 65 | "last 2 versions" 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')() 4 | ] 5 | }; -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/App.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 51 | 52 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/items/common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/items/common.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/items/events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/items/events.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/items/rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/items/rare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/items/ultrarare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/items/ultrarare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/items/uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/items/uncommon.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/items/veryrare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/items/veryrare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/logo.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/main.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | } 8 | 9 | div.container, div.content-container { 10 | width: 100%; 11 | max-width: 960px; 12 | margin: 0 auto; 13 | } 14 | 15 | div.content-container { 16 | margin-top: 40px; 17 | 18 | h1, h2, h3, h4 { 19 | color: inherit; 20 | } 21 | } 22 | 23 | div.ant-list-empty-text { 24 | color: white; 25 | 26 | & div.ant-empty-image > img { 27 | color: black; 28 | } 29 | } 30 | 31 | div.ant-select { 32 | &.ant-select-disabled { 33 | & > div.ant-select-selection { 34 | color: rgba(255, 255, 255, .6); 35 | 36 | &:hover { 37 | border-color: rgba(0,0,0,.1) !important; 38 | } 39 | } 40 | } 41 | 42 | & > div.ant-select-selection { 43 | background: rgba(0,0,0,.25); 44 | border-color: rgba(0,0,0,.1); 45 | color: white; 46 | 47 | &:hover { 48 | border-color: #1890ff; 49 | } 50 | 51 | & span.ant-select-arrow { 52 | color: white; 53 | } 54 | } 55 | } 56 | 57 | div.ant-select-dropdown { 58 | background: #212326; 59 | color: rgba(255,255,255,.6); 60 | 61 | & li.ant-select-dropdown-menu-item { 62 | color: inherit; 63 | 64 | &.ant-select-dropdown-menu-item-selected { 65 | color: #fff; 66 | background: rgba(255,255,255,.05); 67 | } 68 | 69 | &:hover { 70 | background: rgba(255,255,255,.05); 71 | } 72 | } 73 | } 74 | 75 | .ant-alert-with-description, .ant-alert-message { 76 | color: inherit !important; 77 | } 78 | 79 | .ant-alert-message { 80 | font-weight: 600; 81 | } 82 | 83 | .ant-alert-description { 84 | color: inherit; 85 | } 86 | 87 | .ant-alert-error { 88 | background: rgba(255,0,0,0.3) !important; 89 | border: 1px solid rgba(255,0,0,0.35) !important; 90 | color: white !important; 91 | } 92 | 93 | div.news-item { 94 | padding: 20px; 95 | background: rgba(0,0,0,0.4); 96 | color: white; 97 | border-radius: 5px; 98 | margin-bottom: 20px; 99 | border: 1px solid rgba(0,0,0,0.2) !important; 100 | 101 | & > div { 102 | display: flex; 103 | flex-direction: column; 104 | 105 | & > div.image-container { 106 | height: 118px; 107 | margin: -20px -20px 20px -20px; 108 | border-top-left-radius: 5px; 109 | border-top-right-radius: 5px; 110 | background: rgba(0,0,0,0.7); 111 | 112 | & > img { 113 | border-top-left-radius: 5px; 114 | width: 40%; 115 | } 116 | } 117 | 118 | & > span.title { 119 | border-bottom: 1px solid rgba(255,255,255,0.1); 120 | padding-bottom: 15px; 121 | font-weight: 700; 122 | } 123 | 124 | & > span.body { 125 | padding: 20px; 126 | 127 | & li:not(:last-child) { 128 | list-style: disc; 129 | margin-left: 15px; 130 | } 131 | } 132 | } 133 | } 134 | 135 | span.perk-formatted { 136 | color: white; 137 | font-weight: 600; 138 | } 139 | span.perk-level-0 { 140 | color: #199b1e; 141 | } 142 | span.perk-level-1 { 143 | color: #ac3ee3 144 | } 145 | span.perk-level-2 { 146 | color: #ff0955; 147 | } 148 | 149 | div.characters-container { 150 | div.ant-row > div { 151 | width: max-content !important; 152 | } 153 | } 154 | 155 | div.character-info { 156 | & div.specificInfo { 157 | div.ant-tabs-bar { 158 | border-color: rgba(255,255,255,0.05); 159 | } 160 | 161 | div.ant-tabs-tab-disabled { 162 | color: rgba(255,255,255,0.25); 163 | } 164 | } 165 | 166 | div.desc { 167 | & li { 168 | list-style: disc; 169 | margin-left: 15px; 170 | } 171 | } 172 | } 173 | 174 | div.tunable-container > div.ant-table-wrapper { 175 | & div.ant-table-content { 176 | background: rgba(0,0,0,0.2); 177 | } 178 | 179 | & div.ant-table { 180 | color: rgba(255,255,255,0.8); 181 | } 182 | 183 | & .ant-table-thead > tr > th { 184 | color: white; 185 | background: rgba(0,0,0,0.2); 186 | border-bottom-color: rgba(255,255,255,0.1); 187 | } 188 | 189 | & .ant-table-tbody > tr > td { 190 | border-bottom: 1px solid rgba(255,255,255,0.05); 191 | } 192 | 193 | & tr.ant-table-row { 194 | &:hover > td { 195 | background: rgba(255,255,255,0.1) !important; 196 | } 197 | } 198 | 199 | & ul.ant-pagination { 200 | margin-top: 8px; 201 | 202 | & .ant-pagination-item-link, & .ant-pagination-item { 203 | background: rgba(0, 0, 0, 0.2); 204 | 205 | &:hover { 206 | color: #1890ff; 207 | border-color: #1890ff; 208 | } 209 | 210 | &:not(.ant-pagination-item-active) > a, &:not(.ant-pagination-item-active) { 211 | color: white; 212 | } 213 | 214 | &:not(.ant-pagination-item-active) { 215 | border-color: rgba(0, 0, 0, 0.1); 216 | } 217 | } 218 | } 219 | } 220 | 221 | div.perk-list-container, div.offering-list-container { 222 | & .desc { 223 | li { 224 | list-style: disc; 225 | margin-left: 15px; 226 | } 227 | 228 | /* TODO: Finish these */ 229 | span.Highlight1 { 230 | 231 | } 232 | span.Highlight2 { 233 | //color: #ff8800; 234 | } 235 | span.Highlight2, span.Highlight3 { 236 | //color: #d41c1c; 237 | font-weight: 700; 238 | } 239 | span.Highlight4 { 240 | 241 | } 242 | span.FlavorText { 243 | display: block; 244 | font-style: italic; 245 | color: #e3d1b6; 246 | } 247 | } 248 | } -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/offerings/common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/offerings/common.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/offerings/rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/offerings/rare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/offerings/ultrarare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/offerings/ultrarare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/offerings/uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/offerings/uncommon.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/offerings/veryrare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/offerings/veryrare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/common.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/old/common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/old/common.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/old/rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/old/rare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/old/teachable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/old/teachable.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/old/ultrarare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/old/ultrarare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/old/uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/old/uncommon.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/old/veryrare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/old/veryrare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/rare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/teachable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/teachable.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/ultrarare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/ultrarare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/uncommon.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/assets/perks/veryrare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OutOfBears/DBD-API/5399776c4a37b16d32bc33226283958dfa6a6394/DBD-API/ClientApp/src/assets/perks/veryrare.png -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/components/AddonsList.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 60 | 61 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/components/OfferingList.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 69 | 70 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/components/PerkList.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 95 | 96 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/components/TunablesTable.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 65 | 66 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DBD Stats 8 | 9 | 10 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App"; 3 | import router from "./router"; 4 | import Antd from 'ant-design-vue'; 5 | import VueMoment from 'vue-moment' 6 | import moment from 'moment-timezone' 7 | import 'ant-design-vue/dist/antd.css'; 8 | 9 | Vue.config.productionTip = false; 10 | Vue.$router = router; 11 | Vue.use(Antd); 12 | Vue.use(VueMoment, { moment }); 13 | 14 | console.log(Vue.$moment); 15 | 16 | new Vue({ 17 | router, 18 | render: h => h(App), 19 | }).$mount("#app"); 20 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | 4 | /* Lazy loaded components */ 5 | const News = () => import(/* webpackChunkName: "views" */ './views/News'); 6 | const Perks = () => import(/* webpackChunkName: "views" */ './views/Perks'); 7 | const Shrine = () => import(/* webpackChunkName: "views" */ './views/Shrine'); 8 | const Offerings = () => import(/* webpackChunkName: "views" */ './views/Offerings'); 9 | const Characters = () => import(/* webpackChunkName: "views" */ './views/Characters'); 10 | const CharacterInfo = () => import(/* webpackChunkName: "views" */ './views/CharacterInfo'); 11 | 12 | Vue.use(Router); 13 | 14 | const router = new Router({ 15 | mode: 'history', 16 | routes: [ 17 | { path: '/', name: 'characters', component: Characters, }, 18 | { path: '/characters/:id', name: 'character-info', component: CharacterInfo, }, 19 | { path: '/perks', name: 'perks', component: Perks }, 20 | { path: '/offerings', name: 'offerings', component: Offerings }, 21 | { path: '/shrine', name: 'shrine', component: Shrine }, 22 | { path: '/new', name: 'news', component: News }, 23 | ] 24 | }); 25 | 26 | export default router; 27 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/services/ApiService.js: -------------------------------------------------------------------------------- 1 | const urlBase = process.env.NODE_ENV === "development" ? 2 | "https://localhost:5001" : `${window.location.protocol}//${window.location.host}`; 3 | 4 | const apiBase = `${urlBase}/api`; 5 | 6 | const convertBranch = (branch) => { 7 | switch(branch) { 8 | case "live": 9 | case "Public": 10 | return "Public"; 11 | default: 12 | return ""; 13 | } 14 | }; 15 | 16 | const urlEncode = (params) => { 17 | let data = Object.entries(params); 18 | data = data.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`); 19 | return data.join('&'); 20 | }; 21 | 22 | const lazyRequest = (branch, endpoint, extraData = undefined) => { 23 | branch = encodeURIComponent(branch); 24 | 25 | let url = `${apiBase}/${endpoint}?branch=${branch}`; 26 | if(typeof extraData === 'object') 27 | url = `${url}&${urlEncode(extraData)}`; 28 | 29 | return new Promise((res, rej) => { 30 | fetch(url) 31 | .then(resp => { 32 | if(resp.status === 200) 33 | return resp.json(); 34 | throw "Invalid server response"; 35 | }) 36 | .then(data => { 37 | return res(data); 38 | }) 39 | .catch(rej); 40 | }); 41 | }; 42 | 43 | export default { 44 | // url conversion 45 | getIconUrl: function(branch, url) { 46 | branch = convertBranch(branch); 47 | return `${urlBase}/data/${branch}/${url}`; 48 | }, 49 | 50 | // conversions 51 | convertPlayerRole: function(role){ 52 | switch(role) { 53 | case "EPlayerRole::VE_Slasher": 54 | return "Killer"; 55 | case "EPlayerRole::VE_Camper": 56 | return "Survivor"; 57 | default: 58 | return ""; 59 | } 60 | }, 61 | convertGender: function(gender) { 62 | switch(gender) { 63 | case "EGender::VE_Male": 64 | return "Male"; 65 | case "EGender::VE_Female": 66 | return "Female"; 67 | case "EGender::VE_Multiple": 68 | return "Male/Female"; 69 | case "EGender::VE_NotHuman": 70 | return "Monster"; 71 | default: 72 | return ""; 73 | } 74 | }, 75 | convertKillerHeight: function(height){ 76 | switch(height){ 77 | case "EKillerHeight::Average": 78 | return "Average"; 79 | case "EKillerHeight::Short": 80 | return "Short"; 81 | case "EKillerHeight::Tall": 82 | return "Tall"; 83 | default: 84 | return ""; 85 | } 86 | }, 87 | convertCharacterDifficulty: function(difficulty){ 88 | switch(difficulty) { 89 | case "ECharacterDifficulty::VE_Easy": 90 | return "Easy"; 91 | 92 | case "ECharacterDifficulty::VE_Intermediate": 93 | return "Intermediate"; 94 | 95 | case "ECharacterDifficulty::VE_Hard": 96 | return "Hard"; 97 | 98 | default: 99 | return ""; 100 | } 101 | }, 102 | 103 | // api calls 104 | getDbdNews: function(branch = "live") { 105 | branch = encodeURIComponent(branch); 106 | return new Promise((res, rej) => { 107 | lazyRequest(branch, "news") 108 | .then(data => { 109 | data = (data || {}).news || []; 110 | data = data.sort((a, b) => { 111 | return b.weight - a.weight; 112 | }); 113 | return res(data); 114 | }) 115 | .catch(rej); 116 | }) 117 | }, 118 | getItem: (id, branch = "live") => lazyRequest(branch, `items/${id}`), 119 | getItems: (branch = "live") => lazyRequest(branch, "items"), 120 | getItemAddons: (branch = "live") => lazyRequest(branch, "itemaddons"), 121 | getCharacters: (branch = "live") => lazyRequest(branch, "characters"), 122 | getCharacterByIndex: (id, branch = "live") => lazyRequest(branch, `characters/${id}`), 123 | getCharacterPerks: (id, branch = "live") => lazyRequest(branch, `perks/${id}`), 124 | getPerks: (branch = "live") => lazyRequest(branch, "perks"), 125 | getTunables: (branch = "live") => lazyRequest(branch, "tunables"), 126 | getOfferings: (branch = "live") => lazyRequest(branch, "offerings"), 127 | getShrine: (branch = "live") => lazyRequest(branch, "shrineofsecrets"), 128 | 129 | getKillerTunables: function(branch, killer) { 130 | return new Promise((res, rej) => { 131 | this.getTunables(branch) 132 | .then(data => { 133 | let tunables = { 134 | baseTunables: (data.baseTunables || {}).Killer || {}, 135 | tunableValues: (data.killerTunables || {})[killer] || {}, 136 | tunables: (data.knownTunableValues || {})[killer] || {}, 137 | }; 138 | 139 | res(tunables); 140 | }) 141 | .catch(rej) 142 | }); 143 | }, 144 | 145 | getSurvivorItems: function(branch) { 146 | return new Promise((res, rej) => { 147 | this.getItems(branch) 148 | .then(data => { 149 | let items = Object.values(data).filter(x => { 150 | return x.role === "EPlayerRole::VE_Camper"; 151 | }); 152 | 153 | res(items); 154 | }) 155 | .catch(rej); 156 | }); 157 | }, 158 | 159 | getSurvivorAddons: function(branch) { 160 | return new Promise((res, rej) => { 161 | this.getItemAddons(branch) 162 | .then(data => { 163 | let items = Object.values(data).filter(x => { 164 | return x.role === "EPlayerRole::VE_Camper"; 165 | }); 166 | 167 | res(items); 168 | }) 169 | .catch(rej) 170 | }); 171 | }, 172 | 173 | getKillerAddons: function(branch, killerItem) { 174 | return new Promise((res, rej) => { 175 | this.getItemAddons(branch) 176 | .then(data => { 177 | let items = Object.values(data).filter(x => 178 | x.parentItems.indexOf(killerItem) > -1); 179 | res(items); 180 | }) 181 | .catch(rej) 182 | }); 183 | }, 184 | } -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/views/CharacterInfo.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 159 | 160 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/views/Characters.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 71 | 72 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/views/News.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 94 | 95 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/views/Offerings.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 103 | 104 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/views/Perks.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 104 | 105 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/src/views/Shrine.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 118 | 119 | -------------------------------------------------------------------------------- /DBD-API/ClientApp/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); 5 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 6 | 7 | module.exports = { 8 | entry: './src/main.js', 9 | output: { 10 | publicPath: "/", 11 | path: path.resolve(__dirname, './dist'), 12 | filename: 'build.js' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.scss$/, 18 | use: [ 19 | 'vue-style-loader', 20 | 'css-loader', 21 | 'sass-loader' 22 | ] 23 | }, 24 | { 25 | test: /\.vue$/, 26 | use: [{ 27 | loader: 'vue-loader', 28 | options: { 29 | loaders: { 30 | 'scss': 'vue-style-loader!css-loader!resolve-url-loader!sass-loader', 31 | 'sass': 'vue-style-loader!css-loader!resolve-url-loader!sass-loader?indentedSyntax' 32 | } 33 | } 34 | }] 35 | }, 36 | { 37 | test: /\.js$/, 38 | loader: 'babel-loader', 39 | exclude: file => ( 40 | /node_modules/.test(file) && 41 | !/\.vue\.js/.test(file) 42 | ) 43 | }, 44 | { 45 | test: /\.css$/, 46 | use: ['style-loader', 'css-loader', 'postcss-loader'] 47 | }, 48 | { 49 | test: /\.(png|jpg|jpeg|gif|svg|eot|ttf|woff|woff2)$/, 50 | use: [{ 51 | loader: 'url-loader', 52 | options: { 53 | esModule:false, 54 | name: '[folder]/[name].[ext]?[hash]', 55 | limit: 10000 56 | } 57 | }] 58 | } 59 | ] 60 | }, 61 | resolve: { 62 | extensions: ['.js', '.vue'], 63 | modules: [ 64 | 'node_modules' 65 | ], 66 | alias: { 67 | 'vue$': 'vue/dist/vue.esm.js' 68 | } 69 | }, 70 | devServer: { 71 | historyApiFallback: true 72 | }, 73 | devtool: '#eval-source-map', 74 | plugins: [ 75 | new VueLoaderPlugin(), 76 | new HtmlWebpackPlugin({ 77 | filename: 'index.html', 78 | template: 'src/index.html' 79 | }), 80 | new FaviconsWebpackPlugin({ 81 | logo: './src/assets/logo.png', 82 | inject: true, 83 | }) 84 | ] 85 | }; 86 | 87 | if (process.env.NODE_ENV === 'production') { 88 | module.exports.devtool = '#source-map'; 89 | module.exports.plugins = (module.exports.plugins || []).concat([ 90 | new webpack.LoaderOptionsPlugin({ 91 | minimize: true 92 | }) 93 | ]) 94 | } else { 95 | module.exports.plugins = (module.exports.plugins || []).concat([ 96 | new webpack.DefinePlugin({ 97 | 'process.env': { 98 | NODE_ENV: '"development"' 99 | } 100 | }) 101 | ]); 102 | } -------------------------------------------------------------------------------- /DBD-API/DBD-API.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | true 6 | Latest 7 | false 8 | bdb73214-a0d4-41c0-9e0d-e47f2aa17ee4 9 | AnyCPU;x86 10 | ClientApp\ 11 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 12 | 13 | Exe 14 | 15 | 16 | 17 | 18 | x86 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | a\Steamworks.NET.dll 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | %(DistFiles.Identity) 83 | PreserveNewest 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /DBD-API/DBD-API.csproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | MvcControllerEmptyScaffolder 5 | root/Controller 6 | 600 7 | NormalPublish 8 | DBD_API 9 | false 10 | 11 | 12 | ProjectDebugger 13 | 14 | 15 | ProjectDebugger 16 | 17 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DBD_API.Modules.DbD 7 | { 8 | public static class Extensions 9 | { 10 | public static T[] Subset(this T[] array, int start, int count) 11 | { 12 | var result = new T[count]; 13 | Array.Copy(array, start, result, 0, count); 14 | return result; 15 | } 16 | 17 | public static T SearchOne(this IDictionary dict, Predicate test) 18 | { 19 | var result = dict.FirstOrDefault(x => test(x.Value)); 20 | if (!result.Equals(default) && result.Key != null) 21 | return result.Value; 22 | 23 | return default; 24 | } 25 | 26 | public static IEnumerable SearchMany(this IDictionary dict, Predicate test) 27 | { 28 | return dict.Where(x => test(x.Value)) 29 | .Select(x => x.Value); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/Extra.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using ICSharpCode.SharpZipLib; 10 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; 11 | 12 | namespace DBD_API.Modules.DbD 13 | { 14 | public static class Encryption 15 | { 16 | 17 | public static byte[] ReadToEnd(System.IO.Stream stream) 18 | { 19 | long originalPosition = 0; 20 | 21 | if (stream.CanSeek) 22 | { 23 | originalPosition = stream.Position; 24 | stream.Position = 0; 25 | } 26 | 27 | try 28 | { 29 | byte[] readBuffer = new byte[4096]; 30 | 31 | int totalBytesRead = 0; 32 | int bytesRead; 33 | 34 | while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) 35 | { 36 | totalBytesRead += bytesRead; 37 | 38 | if (totalBytesRead == readBuffer.Length) 39 | { 40 | int nextByte = stream.ReadByte(); 41 | if (nextByte != -1) 42 | { 43 | byte[] temp = new byte[readBuffer.Length * 2]; 44 | Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); 45 | Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); 46 | readBuffer = temp; 47 | totalBytesRead++; 48 | } 49 | } 50 | } 51 | 52 | byte[] buffer = readBuffer; 53 | if (readBuffer.Length != totalBytesRead) 54 | { 55 | buffer = new byte[totalBytesRead]; 56 | Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); 57 | } 58 | return buffer; 59 | } 60 | finally 61 | { 62 | if (stream.CanSeek) 63 | { 64 | stream.Position = originalPosition; 65 | } 66 | } 67 | } 68 | 69 | public static string RawDecrypt(string text, string key) 70 | { 71 | byte[] cipher = Convert.FromBase64String(text); 72 | byte[] btkey = Encoding.ASCII.GetBytes(key); 73 | 74 | //init AES 128 75 | RijndaelManaged aes128 = new RijndaelManaged(); 76 | aes128.Mode = CipherMode.ECB; 77 | aes128.Padding = PaddingMode.Zeros; 78 | 79 | //decrypt 80 | ICryptoTransform decryptor = aes128.CreateDecryptor(btkey, null); 81 | MemoryStream ms = new MemoryStream(cipher); 82 | CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); 83 | 84 | byte[] plain = new byte[cipher.Length]; 85 | int decryptcount = cs.Read(plain, 0, plain.Length); 86 | 87 | ms.Close(); 88 | cs.Close(); 89 | 90 | //return plaintext in String 91 | return Encoding.UTF8.GetString(plain, 0, decryptcount); 92 | } 93 | 94 | public static string DecryptCdn(string content, string key) 95 | { 96 | content = content.Substring(8).Trim(); 97 | 98 | var decrypted = RawDecrypt(content, key); 99 | var transformed = ""; 100 | foreach (var t in decrypted) 101 | transformed += (char)(t + 1); 102 | 103 | transformed = transformed.Replace("\x01", ""); 104 | if (!transformed.StartsWith("DbdDAQEB")) return transformed; 105 | 106 | var b64Decoded = Convert.FromBase64String(transformed.Substring(8)); 107 | var decoded = b64Decoded.Subset(4, b64Decoded.Length - 4); 108 | 109 | var stream = new MemoryStream(); 110 | var inputStream = new InflaterInputStream(new MemoryStream(decoded)); 111 | inputStream.CopyTo(stream); 112 | stream.Position = 0; 113 | 114 | return Encoding.Unicode.GetString(ReadToEnd(stream)); 115 | 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/JsonResponse/Converters/CurrencyIdConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | using DBD_API.Modules.DbD; 8 | 9 | namespace DBD_API.Modules.DbD.JsonResponse.Converters 10 | { 11 | internal class CurrencyIdConverter : JsonConverter 12 | { 13 | public override bool CanConvert(Type t) => t == typeof(CurrencyId) || t == typeof(CurrencyId?); 14 | 15 | public override CurrencyId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 16 | { 17 | var value = reader.GetString(); 18 | switch (value) 19 | { 20 | case "Cells": 21 | return CurrencyId.Cells; 22 | case "HalloweenEventCurrency": 23 | return CurrencyId.HalloweenEventCurrency; 24 | case "LunarNewYearCoins": 25 | return CurrencyId.LunarNewYearCoins; 26 | case "Shards": 27 | return CurrencyId.Shards; 28 | } 29 | 30 | throw new Exception("Cannot unmarshal type CurrencyId"); 31 | } 32 | 33 | public override void Write(Utf8JsonWriter writer, CurrencyId value, JsonSerializerOptions options) 34 | { 35 | switch (value) 36 | { 37 | case CurrencyId.Cells: 38 | writer.WriteStringValue("Cells"); 39 | return; 40 | case CurrencyId.HalloweenEventCurrency: 41 | writer.WriteStringValue("HalloweenEventCurrency"); 42 | return; 43 | case CurrencyId.LunarNewYearCoins: 44 | writer.WriteStringValue("LunarNewYearCoins"); 45 | return; 46 | case CurrencyId.Shards: 47 | writer.WriteStringValue("Shards"); 48 | return; 49 | } 50 | 51 | throw new Exception("Cannot marshal type CurrencyId"); 52 | } 53 | 54 | public static readonly CurrencyIdConverter Singleton = new CurrencyIdConverter(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/JsonResponse/Converters/DateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | using System.Diagnostics; 8 | using System.Globalization; 9 | 10 | namespace DBD_API.Modules.DbD.JsonResponse.Converters 11 | { 12 | internal class DateTimeConverter : JsonConverter 13 | { 14 | public static DateTimeConverter Singleton = new DateTimeConverter(); 15 | 16 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 17 | { 18 | Debug.Assert(typeToConvert == typeof(DateTime)); 19 | return DateTime.Parse(reader.GetString(), styles: DateTimeStyles.AssumeUniversal); 20 | } 21 | 22 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) 23 | { 24 | writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ")); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/JsonResponse/ShrineResponse.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | 5 | namespace DBD_API.Modules.DbD.JsonResponse 6 | { 7 | using System; 8 | using System.Globalization; 9 | using System.Diagnostics; 10 | using DBD_API.Modules.DbD.JsonResponse.Converters; 11 | using System.Text.Json.Serialization; 12 | using System.Text.Json; 13 | 14 | public partial class ShrineResponse 15 | { 16 | [JsonPropertyName("items")] 17 | public Item[] Items { get; set; } 18 | 19 | [JsonPropertyName("startDate")] 20 | public DateTime StartDate { get; set; } 21 | 22 | [JsonPropertyName("endDate")] 23 | public DateTime EndDate { get; set; } 24 | 25 | [JsonPropertyName("week")] 26 | public long Week { get; set; } 27 | } 28 | 29 | public partial class Item 30 | { 31 | public string Name { get; set; } 32 | 33 | [JsonPropertyName("id")] 34 | public string Id { get; set; } 35 | 36 | [JsonPropertyName("bloodpointValue")] 37 | public long BloodpointValue { get; set; } 38 | 39 | [JsonPropertyName("experienceValue")] 40 | public long ExperienceValue { get; set; } 41 | 42 | [JsonPropertyName("unique")] 43 | public bool Unique { get; set; } 44 | 45 | [JsonPropertyName("purchased")] 46 | public bool Purchased { get; set; } 47 | 48 | [JsonPropertyName("version")] 49 | public string Version { get; set; } 50 | 51 | [JsonPropertyName("cost")] 52 | public Cost[] Cost { get; set; } 53 | } 54 | 55 | public partial class Cost 56 | { 57 | [JsonPropertyName("currencyId")] 58 | public string CurrencyId { get; set; } 59 | 60 | [JsonPropertyName("price")] 61 | public long Price { get; set; } 62 | } 63 | 64 | public partial class ShrineConvert 65 | { 66 | public static ShrineResponse FromJson(string json) => JsonSerializer.Deserialize(json, ShrineConverter.Settings); 67 | } 68 | 69 | public static class ShrineConverter 70 | { 71 | public static readonly JsonSerializerOptions Settings = new JsonSerializerOptions 72 | { 73 | WriteIndented = true, 74 | Converters = { DateTimeConverter.Singleton } 75 | }; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/JsonResponse/StoreResponse.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | namespace DBD_API.Modules.DbD.JsonResponse 5 | { 6 | using System; 7 | using System.Globalization; 8 | using System.Diagnostics; 9 | using DBD_API.Modules.DbD.JsonResponse.Converters; 10 | using System.Text.Json.Serialization; 11 | using System.Text.Json; 12 | 13 | 14 | public partial class StoreResponse 15 | { 16 | [JsonPropertyName("outfits")] 17 | public Outfit[] Outfits { get; set; } 18 | } 19 | 20 | public partial class Outfit 21 | { 22 | [JsonPropertyName("id")] 23 | public string Id { get; set; } 24 | 25 | [JsonPropertyName("defaultCost")] 26 | public DefaultCost[] DefaultCost { get; set; } 27 | 28 | [JsonPropertyName("character")] 29 | public string Character { get; set; } 30 | 31 | [JsonPropertyName("storeItems")] 32 | public string[] StoreItems { get; set; } 33 | 34 | [JsonPropertyName("newStartDate")] 35 | public DateTime NewStartDate { get; set; } 36 | 37 | [JsonPropertyName("newEndDate")] 38 | public DateTime NewEndDate { get; set; } 39 | } 40 | 41 | public partial class DefaultCost 42 | { 43 | [JsonPropertyName("currencyId")] 44 | public CurrencyId CurrencyId { get; set; } 45 | 46 | [JsonPropertyName("price")] 47 | public long Price { get; set; } 48 | 49 | [JsonPropertyName("discountPercentage")] 50 | public double DiscountPercentage { get; set; } 51 | } 52 | 53 | public enum CurrencyId { Cells, HalloweenEventCurrency, LunarNewYearCoins, Shards }; 54 | 55 | public partial class StoreConvert 56 | { 57 | public static StoreResponse FromJson(string json) => JsonSerializer.Deserialize(json, StoreConverter.Settings); 58 | } 59 | 60 | internal static class StoreConverter 61 | { 62 | public static readonly JsonSerializerOptions Settings = new JsonSerializerOptions 63 | { 64 | WriteIndented = true, 65 | Converters = 66 | { 67 | DateTimeConverter.Singleton, 68 | CurrencyIdConverter.Singleton 69 | }, 70 | }; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/BaseInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | using TaggedItem = System.Collections.Generic.KeyValuePair; 7 | using Property = UETools.Objects.Interfaces.IProperty; 8 | 9 | namespace DBD_API.Modules.DbD.PakItems 10 | { 11 | using TaggedItemsList = System.Collections.Generic.List; 12 | 13 | public class BaseInfo 14 | { 15 | protected readonly TaggedItemsList _itemList; 16 | 17 | public BaseInfo(TaggedItemsList itemList) 18 | { 19 | _itemList = itemList; 20 | } 21 | 22 | public TaggedItemsList GetAll() 23 | { 24 | return _itemList; 25 | } 26 | 27 | protected void ConvertItem(string name, Action callback) 28 | { 29 | var item = _itemList.FirstOrDefault(x => x.Key == name); 30 | if (item.Equals(default) || !(item.Value is Property value) || !(value.Value is T itemValue)) 31 | return; 32 | 33 | callback(itemValue); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/BaseItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using UETools.Core; 7 | using UETools.Objects.Classes; 8 | using TaggedItem = System.Collections.Generic.KeyValuePair; 9 | using Property = UETools.Objects.Interfaces.IProperty; 10 | 11 | namespace DBD_API.Modules.DbD.PakItems 12 | { 13 | using TaggedItemsList = System.Collections.Generic.List; 14 | 15 | public class BaseItem : BaseInfo 16 | { 17 | [JsonPropertyName("id")] 18 | public string Id { get; private set; } 19 | 20 | [JsonPropertyName("type")] 21 | public string Type { get; private set; } 22 | 23 | [JsonPropertyName("tags")] 24 | public string[] Tags { get; private set; } 25 | 26 | [JsonPropertyName("displayName")] 27 | public string DisplayName { get; private set; } 28 | 29 | [JsonPropertyName("description")] 30 | public string Description { get; private set; } 31 | 32 | [JsonPropertyName("handPosition")] 33 | public string HandPosition { get; private set; } 34 | 35 | [JsonPropertyName("role")] 36 | public string Role { get; private set; } 37 | 38 | [JsonPropertyName("rarity")] 39 | public string Rarity { get; private set; } 40 | 41 | [JsonPropertyName("inventory")] 42 | public bool Inventory { get; private set; } 43 | 44 | [JsonPropertyName("chest")] 45 | public bool Chest { get; private set; } 46 | 47 | [JsonPropertyName("requiredKillerAbility")] 48 | public string RequiredKillerAbility { get; private set; } 49 | 50 | [JsonPropertyName("isInNonViolentBuild")] 51 | public bool IsInNonViolentBuild { get; private set; } 52 | 53 | [JsonPropertyName("isAvailableInAtlantaBuild")] 54 | public bool IsAvailableInAtlantaBuild { get; private set; } 55 | 56 | [JsonPropertyName("antiDLC")] 57 | public bool AntiDLC { get; private set; } 58 | 59 | [JsonPropertyName("bloodWeb")] 60 | public bool BloodWeb { get; private set; } 61 | 62 | [JsonPropertyName("iconPathList")] 63 | public string[] IconPathList { get; private set; } 64 | 65 | 66 | public BaseItem(TaggedItemsList itemList) : base(itemList) 67 | { 68 | ConvertItem>("Tags", x => Tags = x.Select(x => (x.Value as FName)?.Name.Value).ToArray()); 69 | ConvertItem("HandPosition", x => HandPosition = x.Name); 70 | ConvertItem("Role", x => Role = x.Name); 71 | ConvertItem("Rarity", x => Rarity = x.Name); 72 | ConvertItem("Inventory", x => Inventory = x); 73 | ConvertItem("Chest", x => Chest = x); 74 | ConvertItem("RequiredKillerAbility", x => RequiredKillerAbility = x.Name); 75 | ConvertItem("IsInNonViolentBuild", x => IsInNonViolentBuild = x); 76 | ConvertItem("IsAvailableInAtlantaBuild", x => IsAvailableInAtlantaBuild = x); 77 | ConvertItem("AntiDLC", x => AntiDLC = x); 78 | ConvertItem("Bloodweb", x => BloodWeb = x); 79 | ConvertItem("ID", x => Id = x.Name); 80 | ConvertItem("Type", x => Type = x.Name); 81 | ConvertItem("UIData", x => 82 | { 83 | var item = x.Vars.FirstOrDefault(x => x.Key == "DisplayName"); 84 | if (!item.Equals(default) && (item.Value.Value is FText displayName)) 85 | DisplayName = displayName.ToString(); 86 | 87 | item = x.Vars.FirstOrDefault(x => x.Key == "Description"); 88 | if (!item.Equals(default) && (item.Value.Value is FText description)) 89 | Description = description.ToString(); 90 | 91 | item = x.Vars.FirstOrDefault(x => x.Key == "IconFilePathList"); 92 | if (!item.Equals(default) && (item.Value.Value is List paths)) 93 | IconPathList = paths.Select(y => y.Value.ToString()).ToArray(); 94 | }); 95 | } 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/CharacterInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using SteamKit2.Unified.Internal; 7 | using UETools.Core; 8 | using UETools.Objects.Classes; 9 | using TaggedItem = System.Collections.Generic.KeyValuePair; 10 | using Property = UETools.Objects.Interfaces.IProperty; 11 | 12 | namespace DBD_API.Modules.DbD.PakItems 13 | { 14 | using TaggedItemsList = System.Collections.Generic.List; 15 | 16 | public struct SlideDescription 17 | { 18 | [JsonPropertyName("overview")] 19 | public string Overview { get; set; } 20 | 21 | [JsonPropertyName("playStyle")] 22 | public string Playstyle { get; set; } 23 | } 24 | 25 | public class CharacterInfo : BaseInfo 26 | { 27 | [JsonPropertyName("characterIndex")] 28 | public int CharacterIndex { get; private set; } 29 | 30 | [JsonPropertyName("role")] 31 | public string Role { get; private set; } 32 | 33 | [JsonPropertyName("difficulty")] 34 | public string Difficulty { get; private set; } 35 | 36 | [JsonPropertyName("displayName")] 37 | public string DisplayName { get; private set; } 38 | 39 | [JsonPropertyName("backStory")] 40 | public string Backstory { get; private set; } 41 | 42 | [JsonPropertyName("biography")] 43 | public string Biography { get; private set; } 44 | 45 | [JsonPropertyName("requiredDlcIdString")] 46 | public string RequiredDlcIdString { get; private set; } 47 | 48 | [JsonPropertyName("idName")] 49 | public string IdName { get; private set; } 50 | 51 | [JsonPropertyName("defaultItem")] 52 | public string DefaultItem { get; private set; } 53 | 54 | [JsonPropertyName("isAvailableInNonViolentBuild")] 55 | public bool IsAvailableInNonViolentBuild { get; private set; } 56 | 57 | [JsonPropertyName("isAvailableInAtlantaBuild")] 58 | public bool IsAvailableInAtlantaBuild { get; private set; } 59 | 60 | [JsonPropertyName("platformExclusiveFlag")] 61 | public uint PlatformExclusiveFlag { get; private set; } 62 | 63 | [JsonPropertyName("killerAbilities")] 64 | public string[] KillerAbilities { get; private set; } 65 | 66 | [JsonPropertyName("gender")] 67 | public string Gender { get; private set; } 68 | 69 | [JsonPropertyName("killerHeight")] 70 | public string KillerHeight { get; private set; } 71 | 72 | [JsonPropertyName("iconPath")] 73 | public string IconPath { get; private set; } 74 | 75 | [JsonPropertyName("backgroundPath")] 76 | public string BackgroundPath { get; private set; } 77 | 78 | [JsonPropertyName("slideShowDescriptions")] 79 | public SlideDescription SlideShowDescriptions { get; private set; } 80 | 81 | [JsonIgnore] 82 | public string TunablePath { get; private set; } 83 | 84 | 85 | public CharacterInfo(TaggedItemsList itemList) : base(itemList) 86 | { 87 | ConvertItem("CharacterIndex", x => CharacterIndex = x); 88 | ConvertItem("Role", x => Role = x.Name); 89 | ConvertItem("Difficulty", x => Difficulty= x.Name); 90 | ConvertItem("DisplayName", x => DisplayName = x.ToString()); 91 | ConvertItem("BackStory", x => Backstory = x.ToString()); 92 | ConvertItem("Biography", x => Biography = x.ToString()); 93 | ConvertItem("RequiredDlcIDString", x => RequiredDlcIdString = x.Value); 94 | ConvertItem("IdName", x => IdName = x.Name); 95 | ConvertItem("IsAvailableInNonViolentBuild", x => IsAvailableInNonViolentBuild = x); 96 | ConvertItem("IsAvailableInAtlantaBuild", x => IsAvailableInAtlantaBuild = x); 97 | ConvertItem("PlatformExclusiveFlag", x => PlatformExclusiveFlag = x); 98 | ConvertItem("DefaultItem", x => DefaultItem = x.Name); 99 | ConvertItem("Gender", x => Gender = x.Name); 100 | ConvertItem("KillerHeight", x => KillerHeight = x.Name); 101 | ConvertItem("IconFilePath", x => IconPath = x.Name); 102 | ConvertItem("BackgroundImagePath", x => BackgroundPath = x.Name); 103 | ConvertItem>("KillerAbilities", x => KillerAbilities = x.Select(y => ((FName)y.Value).Name.Value).ToArray()); 104 | ConvertItem("SlideShowDescriptions", x => 105 | { 106 | var overviewStr = ""; 107 | var playStyleStr = ""; 108 | 109 | var item = x.Vars.FirstOrDefault(y => y.Key == "Overview"); 110 | if (!item.Equals(default) && item.Value.Value is FText overview) 111 | overviewStr = overview.ToString(); 112 | 113 | item = x.Vars.FirstOrDefault(y => y.Key == "Playstyle"); 114 | if (!item.Equals(default) && item.Value.Value is FText playStyle) 115 | playStyleStr = playStyle.ToString(); 116 | 117 | SlideShowDescriptions = new SlideDescription() 118 | { 119 | Overview = overviewStr, 120 | Playstyle = playStyleStr 121 | }; 122 | }); 123 | ConvertItem("TunableDB", x => 124 | { 125 | var item = x.Vars.FirstOrDefault(y => y.Key == "AssetPtr"); 126 | if (!item.Equals(default) && item.Value.Value is var softPtr) 127 | TunablePath = softPtr.ToString(); 128 | }); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/CustomItemInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using UETools.Core; 7 | using UETools.Objects.Classes; 8 | using Property = UETools.Objects.Interfaces.IProperty; 9 | using TaggedItem = System.Collections.Generic.KeyValuePair; 10 | 11 | namespace DBD_API.Modules.DbD.PakItems 12 | { 13 | using TaggedItemsList = System.Collections.Generic.List; 14 | 15 | public class CustomItemInfo : BaseInfo 16 | { 17 | [JsonPropertyName("id")] 18 | public string Id { get; private set; } 19 | 20 | [JsonPropertyName("displayName")] 21 | public string DisplayName { get; private set; } 22 | 23 | [JsonPropertyName("description")] 24 | public string Description { get; private set; } 25 | 26 | [JsonPropertyName("category")] 27 | public string Category { get; private set; } 28 | 29 | [JsonPropertyName("rarity")] 30 | public string Rarity { get; private set; } 31 | 32 | [JsonPropertyName("associatedRole")] 33 | public string AssociatedRole { get; private set; } 34 | 35 | [JsonPropertyName("collectionName")] 36 | public string CollectionName { get; private set; } 37 | 38 | [JsonPropertyName("collectionDescription")] 39 | public string CollectionDescription { get; private set; } 40 | 41 | [JsonPropertyName("platformExclusiveFlag")] 42 | public uint PlatformExclusiveFlag { get; private set; } 43 | 44 | [JsonPropertyName("associatedCharacter")] 45 | public int AssociatedCharacter { get; private set; } 46 | 47 | [JsonPropertyName("prestigeUnlockLevel")] 48 | public int PrestigeUnlockLevel { get; private set; } 49 | 50 | [JsonPropertyName("prestigeUnlockDate")] 51 | public string PrestigeUnlockDate { get; private set; } 52 | 53 | [JsonPropertyName("itemIsInStore")] 54 | public bool ItemIsInStore { get; private set; } 55 | 56 | [JsonPropertyName("isNonVioletBuild")] 57 | public bool IsNonVioletBuild { get; private set; } 58 | 59 | [JsonPropertyName("isAvailableInAtlantaBuild")] 60 | public bool IsAvailableInAtlantaBuild { get; private set; } 61 | 62 | public CustomItemInfo(TaggedItemsList itemList) : base(itemList) 63 | { 64 | ConvertItem("ID", x => Id = x.Name); 65 | ConvertItem("Category", x => Category = x.Name); 66 | ConvertItem("Rarity", x => Rarity = x.Name); 67 | ConvertItem("AssociatedRole", x => AssociatedRole = x.Name); 68 | ConvertItem("CollectionName", x => CollectionName = x.ToString()); 69 | ConvertItem("CollectionDescription", x => CollectionDescription = x.ToString()); 70 | ConvertItem("PlatformExclusiveFlag", x => PlatformExclusiveFlag = x); 71 | ConvertItem("AssociatedCharacter", x => AssociatedCharacter = x); 72 | ConvertItem("PrestigeUnlockLevel", x => PrestigeUnlockLevel = x); 73 | ConvertItem("PrestigeUnlockDate", x => PrestigeUnlockDate = x.Value); 74 | ConvertItem("ItemIsInStore", x => ItemIsInStore = x); 75 | ConvertItem("IsNonVioletBuild", x => IsNonVioletBuild = x); 76 | ConvertItem("IsAvailableInAtlantaBuild", x => IsAvailableInAtlantaBuild = x); 77 | ConvertItem("UIData", x => 78 | { 79 | var item = x.Vars.FirstOrDefault(x => x.Key == "DisplayName"); 80 | if (!item.Equals(default) && (item.Value.Value is FText displayName)) 81 | DisplayName = displayName.ToString(); 82 | 83 | item = x.Vars.FirstOrDefault(x => x.Key == "Description"); 84 | if (!item.Equals(default) && (item.Value.Value is FText description)) 85 | Description = description.ToString(); 86 | 87 | }); 88 | 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/ItemAddonInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using UETools.Core; 7 | using UETools.Objects.Classes; 8 | using TaggedItem = System.Collections.Generic.KeyValuePair; 9 | using Property = UETools.Objects.Interfaces.IProperty; 10 | 11 | namespace DBD_API.Modules.DbD.PakItems 12 | { 13 | using TaggedItemsList = System.Collections.Generic.List; 14 | public class ItemAddonInfo : BaseItem 15 | { 16 | [JsonPropertyName("parentItems")] 17 | public string[] ParentItems { get; private set; } 18 | 19 | public ItemAddonInfo(TaggedItemsList itemList) : base(itemList) 20 | { 21 | ConvertItem>("ParentItems", x => ParentItems = x.Select(x => (x.Value as FName)?.Name.Value).ToArray()); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/MapInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using UETools.Core; 7 | using TaggedItem = System.Collections.Generic.KeyValuePair; 8 | using Property = UETools.Objects.Interfaces.IProperty; 9 | 10 | namespace DBD_API.Modules.DbD.PakItems 11 | { 12 | using TaggedItemsList = System.Collections.Generic.List; 13 | public class MapInfo : BaseItem 14 | { 15 | [JsonPropertyName("mapId")] 16 | public string MapId { get; private set; } 17 | 18 | [JsonPropertyName("name")] 19 | public string Name { get; private set; } 20 | 21 | [JsonPropertyName("themeName")] 22 | public string ThemeName { get; private set; } 23 | 24 | [JsonPropertyName("description")] 25 | public string Description { get; private set; } 26 | 27 | [JsonPropertyName("hookMinDistance")] 28 | public double HookMinDistance { get; private set; } 29 | 30 | [JsonPropertyName("hookMinCount")] 31 | public double HookMinCount { get; private set; } 32 | 33 | [JsonPropertyName("hookMaxCount")] 34 | public double HookMaxCount { get; private set; } 35 | 36 | [JsonPropertyName("bookShelvesMinDistance")] 37 | public double BookShelvesMinDistance { get; private set; } 38 | 39 | [JsonPropertyName("bookShelvesMinCount")] 40 | public double BookShelvesMinCount { get; private set; } 41 | 42 | [JsonPropertyName("bookShelvesMaxCount")] 43 | public double BookShelvesMaxCount { get; private set; } 44 | 45 | [JsonPropertyName("livingWorldObjectsMinCount")] 46 | public double LivingWorldObjectsMinCount { get; private set; } 47 | 48 | [JsonPropertyName("livingWorldObjectsMaxCount")] 49 | public double LivingWorldObjectsMaxCount { get; private set; } 50 | 51 | [JsonPropertyName("thumbnailPath")] 52 | public string ThumbnailPath { get; private set; } 53 | 54 | [JsonPropertyName("sortingIndex")] 55 | public double SortingIndex { get; private set; } 56 | 57 | [JsonPropertyName("dlcIDString")] 58 | public string DlcIDString { get; private set; } 59 | 60 | [JsonPropertyName("isInNonViolentBuild")] 61 | public bool IsInNonViolentBuild { get; private set; } 62 | 63 | 64 | public MapInfo(TaggedItemsList itemList) : base(itemList) 65 | { 66 | ConvertItem("MapId", x => MapId = x.Name); 67 | ConvertItem("Name", x => Name = x.ToString()); 68 | ConvertItem("ThemeName", x => ThemeName = x.ToString()); 69 | ConvertItem("Description", x => Description = x.ToString()); 70 | ConvertItem("ThumbnailPath", x => ThumbnailPath = x.Value); 71 | ConvertItem("DlcIDString", x => DlcIDString = x.ToString()); 72 | ConvertItem("IsInNonViolentBuild", x => IsInNonViolentBuild = x); 73 | ConvertItem("SortingIndex", x => SortingIndex = x); 74 | ConvertItem("LivingWorldObjectsMaxCount", x => LivingWorldObjectsMaxCount = x); 75 | ConvertItem("LivingWorldObjectsMinCount", x => LivingWorldObjectsMinCount = x); 76 | ConvertItem("BookShelvesMaxCount", x => BookShelvesMaxCount = x); 77 | ConvertItem("BookShelvesMinCount", x => BookShelvesMinCount = x); 78 | ConvertItem("BookShelvesMinDistance", x => BookShelvesMinDistance = x); 79 | ConvertItem("HookMaxCount", x => HookMaxCount = x); 80 | ConvertItem("HookMinCount", x => HookMinCount = x); 81 | ConvertItem("HookMinDistance", x => HookMinDistance = x); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/OfferingInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using System.Threading.Tasks; 6 | using UETools.Core; 7 | using TaggedItem = System.Collections.Generic.KeyValuePair; 8 | using Property = UETools.Objects.Interfaces.IProperty; 9 | 10 | namespace DBD_API.Modules.DbD.PakItems 11 | { 12 | using TaggedItemsList = System.Collections.Generic.List; 13 | public class OfferingInfo : BaseItem 14 | { 15 | [JsonPropertyName("offeringType")] 16 | public string OfferingType { get; private set; } 17 | 18 | [JsonPropertyName("canUseAfterEventEnd")] 19 | public bool CanUseAfterEventEnd { get; private set; } 20 | 21 | public OfferingInfo(TaggedItemsList itemList) : base(itemList) 22 | { 23 | ConvertItem("OfferingType", x => OfferingType = x.Name); 24 | ConvertItem("CanUseAfterEventEnd", x => CanUseAfterEventEnd = x); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/PerkInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Json.Serialization; 5 | using UETools.Core; 6 | using UETools.Objects.Classes; 7 | using TaggedItem = System.Collections.Generic.KeyValuePair; 8 | using Property = UETools.Objects.Interfaces.IProperty; 9 | 10 | namespace DBD_API.Modules.DbD.PakItems 11 | { 12 | using TaggedItemsList = System.Collections.Generic.List; 13 | 14 | public class PerkInfo : BaseItem 15 | { 16 | [JsonPropertyName("associatedPlayerIndex")] 17 | public int AssociatedPlayerIndex { get; private set; } 18 | 19 | [JsonPropertyName("mandatoryOnBloodWebLevel")] 20 | public int MandatoryOnBloodweblevel { get; private set; } 21 | 22 | [JsonPropertyName("teachableOnBloodWebLevel")] 23 | public int TeachableOnBloodweblevel { get; private set; } 24 | 25 | [JsonPropertyName("atlantaTeachableLevel")] 26 | public int AtlantaTeachableLevel { get; private set; } 27 | 28 | [JsonPropertyName("perkCategory")] 29 | public string[] PerkCategory { get; private set; } 30 | 31 | [JsonPropertyName("perkLevelRarity")] 32 | public string[] PerkLevelRarity { get; private set; } 33 | 34 | [JsonPropertyName("perkLevelTunables")] 35 | public string[][] PerkLevelTunables { get; private set; } 36 | 37 | [JsonPropertyName("perkDefaultDescription")] 38 | public string PerkDefaultDescription { get; private set; } 39 | 40 | [JsonPropertyName("perkLevel1Description")] 41 | public string PerkLevel1Description { get; private set; } 42 | 43 | [JsonPropertyName("perkLevel2Description")] 44 | public string PerkLevel2Description { get; private set; } 45 | 46 | [JsonPropertyName("perkLevel3Description")] 47 | public string PerkLevel3Description { get; private set; } 48 | 49 | [JsonPropertyName("perkUnlicensedDescriptionOverride")] 50 | public string PerkUnlicensedDescriptionOverride { get; private set; } 51 | 52 | [JsonPropertyName("iconPathList")] 53 | public string[] IconPathList { get; private set; } 54 | 55 | public PerkInfo(TaggedItemsList itemList) : base(itemList) 56 | { 57 | ConvertItem("AssociatedPlayerIndex", x => AssociatedPlayerIndex = x); 58 | ConvertItem("MandatoryOnBloodweblevel", x => MandatoryOnBloodweblevel = x); 59 | ConvertItem("TeachableOnBloodweblevel", x => TeachableOnBloodweblevel = x); 60 | ConvertItem("AtlantaTeachableLevel", x => AtlantaTeachableLevel = x); 61 | ConvertItem>("PerkCategory", x => PerkCategory = x.Select(x => (x.Value as FName)?.Name.Value) .ToArray()); 62 | ConvertItem>("PerkLevelRarity", x => PerkLevelRarity = x.Select(x => (x.Value as FName)?.Name.Value).ToArray()); 63 | ConvertItem("PerkDefaultDescription", x => PerkDefaultDescription = x.ToString()); 64 | ConvertItem("PerkLevel1Description", x => PerkLevel1Description = x.ToString()); 65 | ConvertItem("PerkLevel2Description", x => PerkLevel2Description = x.ToString()); 66 | ConvertItem("PerkLevel3Description", x => PerkLevel3Description = x.ToString()); 67 | ConvertItem("PerkUnlicensedDescriptionOverride", x => PerkUnlicensedDescriptionOverride = x.ToString()); 68 | ConvertItem("UIData", x => 69 | { 70 | var item = x.Vars.FirstOrDefault(x => x.Key == "IconFilePathList"); 71 | if (!item.Equals(default) && (item.Value.Value is List paths)) 72 | IconPathList = paths.Select(y => y.Value.ToString()).ToArray(); 73 | }); 74 | ConvertItem>("PerkLevelTunables", x => 75 | { 76 | PerkLevelTunables = x.Select(y => 77 | { 78 | var props = ((y.Value as TaggedObject)?.Vars.FirstOrDefault(z => z.Key == "Tunables").Value 79 | .Value as List); 80 | return props.Select(f => (f.Value as FString).Value).ToArray(); 81 | }).ToArray(); 82 | }); 83 | } 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /DBD-API/Modules/DbD/PakItems/TunableInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Threading.Tasks; 9 | using UETools.Core; 10 | using UETools.Objects.Classes; 11 | using TaggedItem = System.Collections.Generic.KeyValuePair; 12 | 13 | namespace DBD_API.Modules.DbD.PakItems 14 | { 15 | using TaggedItemsList = System.Collections.Generic.List; 16 | using TunableInfos = ConcurrentDictionary; 17 | 18 | public class TunableContainer 19 | { 20 | [JsonPropertyName("baseTunables")] 21 | public TunableInfos BaseTunables { get; private set; } 22 | 23 | [JsonPropertyName("killerTunables")] 24 | public TunableInfos KillerTunables { get; private set; } 25 | 26 | [JsonPropertyName("knownTunableValues")] 27 | public TunableInfos KnownTunableValues { get; private set; } 28 | 29 | [JsonPropertyName("unknownTunableValues")] 30 | public TunableInfos UnknownTunableValues { get; private set; } 31 | 32 | public TunableContainer() 33 | { 34 | BaseTunables = new TunableInfos(); 35 | KillerTunables = new TunableInfos(); 36 | KnownTunableValues = new TunableInfos(); 37 | UnknownTunableValues = new TunableInfos(); 38 | } 39 | } 40 | 41 | public class Tunable 42 | { 43 | [JsonPropertyName("value")] 44 | public object Value { get; private set; } 45 | 46 | [JsonPropertyName("atlantaOverridenValue")] 47 | public object AtlantaOverridenValue { get; private set; } 48 | 49 | [JsonPropertyName("description")] 50 | public string Description { get; private set; } 51 | 52 | [JsonPropertyName("descriptorTags")] 53 | public string DescriptorTags { get; private set; } 54 | 55 | [JsonPropertyName("overridenInAtlanta")] 56 | public bool OverridenInAtlanta { get; private set; } 57 | 58 | public Tunable(object value, object atlantaValue, string description, 59 | string descTags, bool overridden) 60 | { 61 | Value = value; 62 | AtlantaOverridenValue = atlantaValue; 63 | Description = description; 64 | DescriptorTags = descTags; 65 | OverridenInAtlanta = overridden; 66 | } 67 | } 68 | 69 | [JsonConverter(typeof(TunableSerializer))] 70 | public class TunableInfo 71 | { 72 | public ConcurrentDictionary Tunables { get; private set; } 73 | 74 | public TunableInfo(TaggedItemsList itemList) 75 | { 76 | Tunables = new ConcurrentDictionary(); 77 | 78 | foreach (var item in itemList) 79 | { 80 | if (item.Value == null || string.IsNullOrEmpty(item.Key)) 81 | continue; 82 | 83 | object value = null; 84 | object atlantaValue = null; 85 | var description = ""; 86 | var descriptorTags = ""; 87 | var overriden = false; 88 | 89 | var obj = item.Value; 90 | var temp = obj.Vars.FirstOrDefault(x => x.Key == "Value"); 91 | if (!temp.Equals(default) && temp.Value != null) value = temp.Value.Value; 92 | 93 | temp = obj.Vars.FirstOrDefault(x => x.Key == "AtlantaOverriddenValue"); 94 | if (!temp.Equals(default) && temp.Value != null) atlantaValue = temp.Value.Value; 95 | 96 | temp = obj.Vars.FirstOrDefault(x => x.Key == "Description"); 97 | if (!temp.Equals(default) && temp.Value != null && temp.Value.Value is FString desc) 98 | description = desc.Value; 99 | 100 | temp = obj.Vars.FirstOrDefault(x => x.Key == "DescriptorTags"); 101 | if (!temp.Equals(default) && temp.Value != null && temp.Value.Value is FString descTags) 102 | descriptorTags = descTags.Value; 103 | 104 | temp = obj.Vars.FirstOrDefault(x => x.Key == "DescriptorTags"); 105 | if (!temp.Equals(default) && temp.Value != null && temp.Value.Value is bool overridenval) 106 | overriden = overridenval; 107 | 108 | Tunables[item.Key] = new Tunable(value, atlantaValue, description, descriptorTags, overriden); 109 | } 110 | } 111 | } 112 | 113 | public class TunableSerializer : JsonConverter 114 | { 115 | public override TunableInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 116 | { 117 | throw new NotImplementedException(); 118 | } 119 | 120 | public override void Write(Utf8JsonWriter writer, TunableInfo value, JsonSerializerOptions options) 121 | { 122 | writer.WriteStartObject(); 123 | foreach (var item in value.Tunables) 124 | { 125 | if (options.IgnoreNullValues && item.Value == null) 126 | continue; 127 | 128 | writer.WritePropertyName(item.Key); 129 | JsonSerializer.Serialize(writer, item.Value, options); 130 | 131 | } 132 | writer.WriteEndObject(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /DBD-API/Modules/SPA/VueHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.NetworkInformation; 7 | using System.Runtime.InteropServices; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Builder; 10 | using Microsoft.AspNetCore.SpaServices; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace DBD_API.Modules.SPA 15 | { 16 | public static class VueHelper 17 | { 18 | 19 | private static int Port { get; } = 8080; 20 | private static Uri DevelopmentServerEndpoint { get; } = new Uri($"http://localhost:{Port}"); 21 | private static TimeSpan Timeout { get; } = TimeSpan.FromSeconds(30); 22 | 23 | private static string DoneMessage { get; } = "DONE Compiled successfully in"; 24 | 25 | public static void UseVueDevelopmentServer(this ISpaBuilder spa) 26 | { 27 | spa.UseProxyToSpaDevelopmentServer(async () => 28 | { 29 | var loggerFactory = spa.ApplicationBuilder.ApplicationServices.GetService(); 30 | var logger = loggerFactory.CreateLogger("Vue"); 31 | 32 | if (IsRunning()) 33 | { 34 | return DevelopmentServerEndpoint; 35 | } 36 | 37 | // launch vue.js development server 38 | var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 39 | var processInfo = new ProcessStartInfo 40 | { 41 | FileName = isWindows ? "cmd" : "npm", 42 | Arguments = $"{(isWindows ? "/c npm " : "")}run serve", 43 | WorkingDirectory = "ClientApp", 44 | RedirectStandardError = true, 45 | RedirectStandardInput = true, 46 | RedirectStandardOutput = true, 47 | UseShellExecute = false, 48 | }; 49 | var process = Process.Start(processInfo); 50 | var tcs = new TaskCompletionSource(); 51 | _ = Task.Run(() => 52 | { 53 | try 54 | { 55 | string line; 56 | while ((line = process.StandardOutput.ReadLine()) != null) 57 | { 58 | logger.LogInformation(line); 59 | if (!tcs.Task.IsCompleted && line.Contains(DoneMessage)) 60 | { 61 | tcs.SetResult(1); 62 | } 63 | } 64 | } 65 | catch (EndOfStreamException ex) 66 | { 67 | logger.LogError(ex.ToString()); 68 | tcs.SetException(new InvalidOperationException("'npm run serve' failed.", ex)); 69 | } 70 | }); 71 | _ = Task.Run(() => 72 | { 73 | try 74 | { 75 | string line; 76 | while ((line = process.StandardError.ReadLine()) != null) 77 | { 78 | logger.LogError(line); 79 | } 80 | } 81 | catch (EndOfStreamException ex) 82 | { 83 | logger.LogError(ex.ToString()); 84 | tcs.SetException(new InvalidOperationException("'npm run serve' failed.", ex)); 85 | } 86 | }); 87 | 88 | var timeout = Task.Delay(Timeout); 89 | if (await Task.WhenAny(timeout, tcs.Task) == timeout) 90 | { 91 | throw new TimeoutException(); 92 | } 93 | 94 | return DevelopmentServerEndpoint; 95 | }); 96 | 97 | } 98 | 99 | private static bool IsRunning() => IPGlobalProperties.GetIPGlobalProperties() 100 | .GetActiveTcpListeners() 101 | .Select(x => x.Port) 102 | .Contains(Port); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /DBD-API/Modules/Steam/ContentManagement/CDNClientPool.cs: -------------------------------------------------------------------------------- 1 | // Based on: https://github.com/SteamRE/DepotDownloader/blob/master/DepotDownloader/CDNClientPool.cs 2 | 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using SteamKit2; 10 | 11 | using CDNAuthTokenCallback = SteamKit2.SteamApps.CDNAuthTokenCallback; 12 | 13 | namespace DBD_API.Modules.Steam.ContentManagement 14 | { 15 | class CDNClientPool 16 | { 17 | private const int ServerEndpointMinimumSize = 8; 18 | 19 | private SteamClient _client; 20 | private SteamApps _apps; 21 | 22 | private readonly ConcurrentBag _activeClientPool; 23 | private readonly ConcurrentDictionary _contentServerPenalty; 24 | private readonly ConcurrentDictionary> _activeClientAuthed; 25 | private readonly BlockingCollection _availableServerEndpoints; 26 | private readonly CancellationTokenSource _cancellation; 27 | 28 | private ConcurrentDictionary _cdnAuthTokens; 29 | private AutoResetEvent _populateEvent; 30 | 31 | 32 | public CDNClientPool(SteamClient client, CancellationTokenSource cancellationToken) 33 | { 34 | // to be supplied by the background service 35 | _cancellation = cancellationToken; 36 | _client = client; 37 | _apps = client.GetHandler(); 38 | 39 | 40 | _populateEvent = new AutoResetEvent(true); 41 | _activeClientPool = new ConcurrentBag(); 42 | _contentServerPenalty = new ConcurrentDictionary(); 43 | _activeClientAuthed = new ConcurrentDictionary>(); 44 | _availableServerEndpoints = new BlockingCollection(); 45 | _cdnAuthTokens = new ConcurrentDictionary(); 46 | 47 | Task.Factory.StartNew(ConnectionPoolMonitor, cancellationToken.Token).Unwrap(); 48 | } 49 | 50 | public async Task> FetchServerList() 51 | { 52 | while (!_cancellation.IsCancellationRequested) 53 | { 54 | try 55 | { 56 | var cdnServers = 57 | await ContentServerDirectoryService.LoadAsync(_client.Configuration, _cancellation.Token); 58 | if (cdnServers != null) 59 | return cdnServers; 60 | } 61 | catch (Exception ex) 62 | { 63 | Console.WriteLine("Failed to grab content server list"); 64 | } 65 | } 66 | 67 | return null; 68 | } 69 | 70 | public async Task ConnectionPoolMonitor() 71 | { 72 | while (!_cancellation.IsCancellationRequested) 73 | { 74 | _populateEvent.WaitOne(TimeSpan.FromSeconds(1)); 75 | 76 | if (_availableServerEndpoints.Count >= ServerEndpointMinimumSize || !_client.IsConnected) 77 | continue; 78 | 79 | var servers = await FetchServerList(); 80 | if (servers == null) 81 | { 82 | return; 83 | } 84 | 85 | var weightedServers = servers.Select(x => 86 | { 87 | _contentServerPenalty.TryGetValue(x.Host, out var penalty); 88 | return Tuple.Create(x, penalty); 89 | }) 90 | .OrderBy(x => x.Item2) 91 | .ThenBy(x => x.Item1.WeightedLoad); 92 | 93 | foreach (var endpoint in weightedServers) 94 | for (var i = 0; i < endpoint.Item1.NumEntries; i++) 95 | _availableServerEndpoints.Add(endpoint.Item1); 96 | } 97 | } 98 | 99 | public async Task GetConnectionForDepotAsync(uint appId, uint depotId, byte[] depotKey, 100 | CancellationToken token) 101 | { 102 | CDNClient client = null; 103 | Tuple authData; 104 | _activeClientPool.TryTake(out client); 105 | 106 | if (client == null) 107 | client = await BuildConnection(appId, depotId, depotKey, null, token); 108 | 109 | if (!_activeClientAuthed.TryGetValue(client, out authData) || authData.Item1 != depotId) 110 | { 111 | if (authData?.Item2.Type == "CDN" || authData?.Item2.Type == "SteamCache") 112 | { 113 | Console.WriteLine("Re-authed CDN connection to content server {0} from {1} to {2}", authData.Item2, authData.Item1, depotId); 114 | await AuthenticateConnection(client, authData.Item2, appId, depotId, depotKey); 115 | } 116 | else if (authData?.Item2.Type == "CS") 117 | { 118 | Console.WriteLine("Re-authed anonymous connection to content server {0} from {1} to {2}", authData.Item2, authData.Item1, depotId); 119 | await AuthenticateConnection(client, authData.Item2, appId, depotId, depotKey); 120 | } 121 | else 122 | { 123 | client = await BuildConnection(appId, depotId, depotKey, authData?.Item2, token); 124 | } 125 | } 126 | 127 | return client; 128 | 129 | } 130 | 131 | 132 | public void ReturnConnection(CDNClient client) 133 | { 134 | if (client == null) return; 135 | _activeClientPool.Add(client); 136 | } 137 | 138 | public void ReturnBrokenConnection(CDNClient client) 139 | { 140 | if (client == null) return; 141 | ReleaseConnection(client); 142 | } 143 | 144 | private void ReleaseConnection(CDNClient client) 145 | { 146 | Tuple authData; 147 | _activeClientAuthed.TryRemove(client, out authData); 148 | } 149 | 150 | private async Task AuthenticateConnection(CDNClient client, CDNClient.Server server, uint appId, 151 | uint depotId, byte[] depotKey) 152 | { 153 | 154 | try 155 | { 156 | string cdnAuthToken = null; 157 | 158 | if (server.Type == "CDN" || server.Type == "SteamCache") 159 | cdnAuthToken = (await RequestCDNAuthToken(appId, depotId, server.Host))?.Token; 160 | 161 | await client.AuthenticateDepotAsync(depotId, depotKey, cdnAuthToken); 162 | _activeClientAuthed[client] = Tuple.Create(depotId, server); 163 | return true; 164 | } 165 | catch (Exception ex) 166 | { 167 | Console.WriteLine("Failed to reauth to content server {0}: {1}", server, ex.Message); 168 | } 169 | 170 | return false; 171 | } 172 | 173 | 174 | private async Task BuildConnection(uint appId, uint depotId, byte[] depotKey, 175 | CDNClient.Server serverSeed, CancellationToken token) 176 | { 177 | CDNClient.Server server = null; 178 | CDNClient client = null; 179 | 180 | while (client == null) 181 | { 182 | if (serverSeed != null) 183 | { 184 | server = serverSeed; 185 | serverSeed = null; 186 | } 187 | else 188 | { 189 | if (_availableServerEndpoints.Count < ServerEndpointMinimumSize) 190 | _populateEvent.Set(); 191 | 192 | server = _availableServerEndpoints.Take(token); 193 | } 194 | 195 | client = new CDNClient(_client); 196 | 197 | try 198 | { 199 | var cdnToken = ""; 200 | 201 | if (server.Type == "CDN" || server.Type == "SteamCache") 202 | cdnToken = (await RequestCDNAuthToken(appId, depotId, server.Host))?.Token; 203 | 204 | await client.ConnectAsync(server); 205 | await client.AuthenticateDepotAsync(depotId, depotKey, cdnToken); 206 | } 207 | catch (Exception ex) 208 | { 209 | client = null; 210 | Console.WriteLine("Failed to connect to content server {0}: {1}", server, ex.Message); 211 | _contentServerPenalty.TryGetValue(server.Host, out var penalty); 212 | _contentServerPenalty[server.Host] = ++penalty; 213 | 214 | } 215 | } 216 | 217 | Console.WriteLine("Initialized connection to content server {0} with depot id {1}", server, depotId); 218 | _activeClientAuthed[client] = Tuple.Create(depotId, server); 219 | 220 | return client; 221 | } 222 | 223 | 224 | private async Task RequestCDNAuthToken(uint appId, uint depotId, string host) 225 | { 226 | host = ResolveCDNTopLevelHost(host); 227 | var cdnKey = $"{depotId:D}:{host}"; 228 | 229 | if (_cdnAuthTokens.TryGetValue(cdnKey, out CDNAuthTokenCallback callback) && callback != null) 230 | return callback; 231 | 232 | callback = await _apps.GetCDNAuthToken(appId, depotId, host); 233 | _cdnAuthTokens[cdnKey] = callback ?? throw new Exception("Failed to get CDN token"); 234 | return callback; 235 | } 236 | 237 | private static string ResolveCDNTopLevelHost(string host) 238 | { 239 | return host.EndsWith(".steampipe.steamcontent.com") ? "steampipe.steamcontent.com" : host; 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /DBD-API/Modules/Steam/ContentManagement/ProtoManifest.cs: -------------------------------------------------------------------------------- 1 | // Based on: https://github.com/SteamRE/DepotDownloader/blob/master/DepotDownloader/CDNClientPool.cs 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.IO.Compression; 7 | using System.Linq; 8 | using System.Security.Cryptography; 9 | using System.Threading.Tasks; 10 | using ProtoBuf; 11 | using SteamKit2; 12 | 13 | namespace DBD_API.Modules.Steam.ContentManagement 14 | { 15 | [ProtoContract()] 16 | class ProtoManifest 17 | { 18 | // Proto ctor 19 | private ProtoManifest() 20 | { 21 | Files = new List(); 22 | } 23 | 24 | public ProtoManifest(DepotManifest sourceManifest, ulong id) : this() 25 | { 26 | sourceManifest.Files.ForEach(f => Files.Add(new FileData(f))); 27 | ID = id; 28 | } 29 | 30 | [ProtoContract()] 31 | public class FileData 32 | { 33 | // Proto ctor 34 | private FileData() 35 | { 36 | Chunks = new List(); 37 | } 38 | 39 | public FileData(DepotManifest.FileData sourceData) : this() 40 | { 41 | FileName = sourceData.FileName; 42 | sourceData.Chunks.ForEach(c => Chunks.Add(ChunkData.Create(c))); 43 | Flags = sourceData.Flags; 44 | TotalSize = sourceData.TotalSize; 45 | FileHash = sourceData.FileHash; 46 | } 47 | 48 | [ProtoMember(1)] 49 | public string FileName { get; internal set; } 50 | 51 | /// 52 | /// Gets the chunks that this file is composed of. 53 | /// 54 | [ProtoMember(2)] 55 | public List Chunks { get; private set; } 56 | 57 | /// 58 | /// Gets the file flags 59 | /// 60 | [ProtoMember(3)] 61 | public EDepotFileFlag Flags { get; private set; } 62 | 63 | /// 64 | /// Gets the total size of this file. 65 | /// 66 | [ProtoMember(4)] 67 | public ulong TotalSize { get; private set; } 68 | 69 | /// 70 | /// Gets the hash of this file. 71 | /// 72 | [ProtoMember(5)] 73 | public byte[] FileHash { get; private set; } 74 | } 75 | 76 | [ProtoContract()] 77 | public class ChunkData 78 | { 79 | public static ChunkData Create(DepotManifest.ChunkData sourceChunk) 80 | { 81 | return new ChunkData 82 | { 83 | ChunkID = sourceChunk.ChunkID, 84 | Checksum = sourceChunk.Checksum, 85 | Offset = sourceChunk.Offset, 86 | CompressedLength = sourceChunk.CompressedLength, 87 | UncompressedLength = sourceChunk.UncompressedLength 88 | }; 89 | } 90 | 91 | /// 92 | /// Gets the SHA-1 hash chunk id. 93 | /// 94 | [ProtoMember(1)] 95 | public byte[] ChunkID { get; private set; } 96 | 97 | /// 98 | /// Gets the expected Adler32 checksum of this chunk. 99 | /// 100 | [ProtoMember(2)] 101 | public byte[] Checksum { get; private set; } 102 | 103 | /// 104 | /// Gets the chunk offset. 105 | /// 106 | [ProtoMember(3)] 107 | public ulong Offset { get; private set; } 108 | 109 | /// 110 | /// Gets the compressed length of this chunk. 111 | /// 112 | [ProtoMember(4)] 113 | public uint CompressedLength { get; private set; } 114 | 115 | /// 116 | /// Gets the decompressed length of this chunk. 117 | /// 118 | [ProtoMember(5)] 119 | public uint UncompressedLength { get; private set; } 120 | } 121 | 122 | [ProtoMember(1)] 123 | public List Files { get; private set; } 124 | 125 | [ProtoMember(2)] 126 | public ulong ID { get; private set; } 127 | 128 | private static byte[] ShaHash(byte[] input) 129 | { 130 | using (var sha = SHA1.Create()) 131 | { 132 | var output = sha.ComputeHash(input); 133 | 134 | return output; 135 | } 136 | } 137 | 138 | public static ProtoManifest LoadFromBuffer(byte[] buffer, out byte[] checksum) 139 | { 140 | using (var uncompressed = new MemoryStream()) 141 | { 142 | using (var ms = new MemoryStream(buffer)) 143 | using (var ds = new DeflateStream(ms, CompressionMode.Decompress)) 144 | ds.CopyTo(uncompressed); 145 | 146 | checksum = ShaHash(uncompressed.ToArray()); 147 | uncompressed.Seek(0, SeekOrigin.Begin); 148 | return ProtoBuf.Serializer.Deserialize(uncompressed); 149 | } 150 | } 151 | 152 | public static ProtoManifest LoadFromFile(string filename, out byte[] checksum) 153 | { 154 | if (!File.Exists(filename)) 155 | { 156 | checksum = null; 157 | return null; 158 | } 159 | 160 | using (MemoryStream ms = new MemoryStream()) 161 | { 162 | using (FileStream fs = File.Open(filename, FileMode.Open)) 163 | using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress)) 164 | ds.CopyTo(ms); 165 | 166 | checksum = ShaHash(ms.ToArray()); 167 | 168 | ms.Seek(0, SeekOrigin.Begin); 169 | return ProtoBuf.Serializer.Deserialize(ms); 170 | } 171 | } 172 | 173 | public void SaveToBuffer(out byte[] buffer, out byte[] checksum) 174 | { 175 | using (var compressed = new MemoryStream()) 176 | { 177 | using (var ms = new MemoryStream()) 178 | { 179 | ProtoBuf.Serializer.Serialize(ms, this); 180 | 181 | checksum = ShaHash(ms.ToArray()); 182 | ms.Seek(0, SeekOrigin.Begin); 183 | 184 | using (var ds = new DeflateStream(compressed, CompressionMode.Compress)) 185 | ms.CopyTo(ds); 186 | } 187 | 188 | buffer = compressed.ToArray(); 189 | } 190 | } 191 | 192 | public void SaveToFile(string filename, out byte[] checksum) 193 | { 194 | 195 | using (MemoryStream ms = new MemoryStream()) 196 | { 197 | ProtoBuf.Serializer.Serialize(ms, this); 198 | 199 | checksum = ShaHash(ms.ToArray()); 200 | 201 | ms.Seek(0, SeekOrigin.Begin); 202 | 203 | using (FileStream fs = File.Open(filename, FileMode.Create)) 204 | using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress)) 205 | ms.CopyTo(ds); 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /DBD-API/Modules/Steam/SteamMatchmaking.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace DBD_API.Modules.Steam 7 | { 8 | // TODO: Implement this 9 | public class SteamMatchmaking 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DBD-API/Modules/Steam/SteamTicketAccepted.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using SteamKit2; 6 | using SteamKit2.Internal; 7 | 8 | namespace DBD_API.Modules.Steam 9 | { 10 | /// 11 | /// This callback is fired when Steam accepts our auth ticket. 12 | /// 13 | public class SteamTicketAccepted : CallbackMsg 14 | { 15 | /// 16 | /// of AppIDs of the games that have generated tickets. 17 | /// 18 | public List AppIDs { get; private set; } 19 | /// 20 | /// of CRC32 hashes of activated tickets. 21 | /// 22 | public List ActiveTicketsCRC { get; private set; } 23 | /// 24 | /// Number of message in sequence. 25 | /// 26 | public uint MessageSequence { get; private set; } 27 | 28 | internal SteamTicketAccepted(JobID jobId, CMsgClientAuthListAck body) 29 | { 30 | JobID = jobId; 31 | AppIDs = body.app_ids; 32 | ActiveTicketsCRC = body.ticket_crc; 33 | MessageSequence = body.message_sequence; 34 | } 35 | } 36 | 37 | public class SteamTicketAuth : ClientMsgHandler 38 | { 39 | public override void HandleMsg(IPacketMsg packetMsg) 40 | { 41 | if (packetMsg.MsgType != EMsg.ClientAuthListAck) 42 | return; 43 | 44 | var authAck = new ClientMsgProtobuf(packetMsg); 45 | var acknowledged = new SteamTicketAccepted(authAck.TargetJobID, authAck.Body); 46 | Client.PostCallback(acknowledged); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DBD-API/Modules/Steam/SteamUserStatsResponse.cs: -------------------------------------------------------------------------------- 1 | using SteamKit2; 2 | using SteamKit2.Internal; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace DBD_API.Modules.Steam 11 | { 12 | /// 13 | /// This callback is fired when steam retrieves the users stats. 14 | /// 15 | public class SteamUserStatsResponse : CallbackMsg 16 | { 17 | public GameID GameId { get; private set; } 18 | 19 | public bool GameIdSpecified { get; private set; } 20 | 21 | public EResult Result { get; private set; } 22 | 23 | public bool ResultSpecified { get; private set; } 24 | 25 | public uint CrcStats { get; private set; } 26 | 27 | public bool CrcStatsSpecified { get; private set; } 28 | 29 | public byte[] Schema { get; private set; } 30 | 31 | public bool SchemaSpecified { get; private set; } 32 | 33 | public List Stats { get; private set; } 34 | 35 | public List AchievementBlocks { get; private set; } 36 | 37 | public KeyValue ParsedSchema; 38 | 39 | internal SteamUserStatsResponse(JobID jobId, CMsgClientGetUserStatsResponse body) 40 | { 41 | JobID = jobId; 42 | GameId = body.game_id; 43 | GameIdSpecified = body.game_idSpecified; 44 | Result = (EResult)body.eresult; 45 | ResultSpecified = body.eresultSpecified; 46 | CrcStats = body.crc_stats; 47 | CrcStatsSpecified = body.crc_statsSpecified; 48 | Schema = body.schema; 49 | SchemaSpecified = body.schemaSpecified; 50 | Stats = body.stats; 51 | AchievementBlocks = body.achievement_blocks; 52 | 53 | ParsedSchema = new KeyValue(); 54 | if (body.schemaSpecified && body.schema != null) 55 | { 56 | using (MemoryStream ms = new MemoryStream(body.schema)) 57 | using (var br = new BinaryReader(ms)) 58 | { 59 | ParsedSchema.TryReadAsBinary(ms); 60 | } 61 | } 62 | } 63 | } 64 | 65 | public class SteamStatsHandler : ClientMsgHandler 66 | { 67 | public override void HandleMsg(IPacketMsg packetMsg) 68 | { 69 | if (packetMsg.MsgType != EMsg.ClientGetUserStatsResponse) 70 | return; 71 | 72 | var statsResp = new ClientMsgProtobuf(packetMsg); 73 | var acknowledged = new SteamUserStatsResponse(statsResp.TargetJobID, statsResp.Body); 74 | Client.PostCallback(acknowledged); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DBD-API/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using System.IO; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace DBD_API 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) => 12 | CreateWebHostBuilder(args) 13 | .Build() 14 | .Run(); 15 | 16 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 17 | WebHost.CreateDefaultBuilder(args) 18 | .ConfigureLogging(logging => 19 | { 20 | logging.ClearProviders(); 21 | logging.AddConsole(); 22 | }) 23 | .ConfigureAppConfiguration((hostingContext, config) => 24 | { 25 | config.AddEnvironmentVariables(); 26 | config.SetBasePath(Directory.GetCurrentDirectory()); 27 | config.AddJsonFile("config.json", optional: true, reloadOnChange: true); 28 | config.AddYamlFile("config.yml", optional: true, reloadOnChange: true); 29 | }) 30 | .UseStartup(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DBD-API/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:63652", 7 | "sslPort": 44392 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "DBD_API": { 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /DBD-API/Services/CacheService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using ICSharpCode.SharpZipLib.Checksum; 8 | using Microsoft.Extensions.Caching.Distributed; 9 | using RestSharp; 10 | 11 | namespace DBD_API.Services 12 | { 13 | public class CacheService 14 | { 15 | private readonly IDistributedCache _cache; 16 | 17 | 18 | public CacheService(IDistributedCache cache) 19 | { 20 | _cache = cache; 21 | } 22 | 23 | public async Task GetCachedRequest(RestRequest request, string branch) 24 | { 25 | var cacheHit = await _cache.GetAsync($"request:{HashRequest(request, branch)}"); 26 | return cacheHit == null ? null : ReadCachedRequest(cacheHit); 27 | } 28 | 29 | public async Task CacheRequest(RestRequest request, IRestResponse response, string branch, int ttl = 1800) 30 | { 31 | if (response == null || !response.IsSuccessful) 32 | return; 33 | 34 | await _cache.SetAsync($"request:{HashRequest(request, branch)}", 35 | CreateCachedRequest(response), new DistributedCacheEntryOptions() 36 | { 37 | AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(ttl) 38 | }); 39 | } 40 | 41 | 42 | private static string HashRequest(RestRequest request, string branch) 43 | { 44 | var hasher = new Crc32(); 45 | using (var rawStream = new MemoryStream()) 46 | using (var byteStream = new BinaryWriter(rawStream)) 47 | { 48 | byteStream.Write(branch); 49 | byteStream.Write((int)request.Method); 50 | byteStream.Write(request.Resource); 51 | byteStream.Write(string.Join("&", request.Parameters 52 | .Where(x => x.Type == ParameterType.QueryString) 53 | .Select(x => $"{x.Name}={x.Value}"))); 54 | 55 | hasher.Update(rawStream.GetBuffer()); 56 | 57 | var hash = hasher.Value.ToString("x"); 58 | return hash; 59 | } 60 | 61 | } 62 | 63 | private static RestResponse ReadCachedRequest(byte[] content) 64 | { 65 | using (var stream = new MemoryStream(content)) 66 | using (var reader = new BinaryReader(stream)) 67 | { 68 | var byteCount = reader.ReadInt64(); 69 | var response = new RestResponse() 70 | { 71 | ResponseStatus = ResponseStatus.Completed, 72 | StatusCode = (HttpStatusCode)reader.ReadUInt32(), 73 | ContentLength = reader.ReadInt64(), 74 | ContentType = reader.ReadString(), 75 | RawBytes = reader.ReadBytes((int)byteCount), 76 | }; 77 | 78 | response.Content = Encoding.Unicode.GetString(response.RawBytes); 79 | 80 | return response; 81 | } 82 | } 83 | 84 | private static byte[] CreateCachedRequest(IRestResponse response) 85 | { 86 | using (var stream = new MemoryStream()) 87 | using (var writer = new BinaryWriter(stream)) 88 | { 89 | writer.Write(response.RawBytes.LongLength); 90 | writer.Write((uint)response.StatusCode); 91 | writer.Write(response.ContentLength); 92 | writer.Write(response.ContentType); 93 | writer.Write(response.RawBytes); 94 | return stream.GetBuffer(); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /DBD-API/Services/DdbService.cs: -------------------------------------------------------------------------------- 1 | using DBD_API.Modules.DbD; 2 | using DBD_API.Modules.DbD.PakItems; 3 | using Microsoft.Extensions.Caching.Distributed; 4 | using Microsoft.Extensions.Configuration; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using RestSharp; 8 | using System; 9 | using System.Collections.Concurrent; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Net; 14 | using System.Security.Cryptography; 15 | using System.Text; 16 | using System.Threading.Tasks; 17 | using DBD_API.Modules.DbD.JsonResponse; 18 | using SteamKit2.GC.Underlords.Internal; 19 | using System.Net.Http; 20 | using Microsoft.Extensions.Logging; 21 | 22 | namespace DBD_API.Services 23 | { 24 | public class DdbService 25 | { 26 | // static 27 | private const string UserAgent = "DeadByDaylight/++DeadByDaylight+Live-CL-296874 Windows/10.0.18363.1.256.64bit"; 28 | 29 | public static readonly string[] AllowedPrefixes = 30 | { 31 | "live", 32 | "ptb", 33 | "stage", 34 | "dev", 35 | "qa", 36 | "cert" 37 | }; 38 | 39 | public ConcurrentDictionary> MapInfos; 40 | public ConcurrentDictionary> PerkInfos; 41 | public ConcurrentDictionary> OfferingInfos; 42 | public ConcurrentDictionary> CustomItemInfos; 43 | public ConcurrentDictionary> CharacterInfos; 44 | public ConcurrentDictionary> ItemAddonInfos; 45 | public ConcurrentDictionary> ItemInfos; 46 | public ConcurrentDictionary TunableInfos; 47 | 48 | private readonly IConfiguration _config; 49 | private readonly ILogger _logger; 50 | 51 | private CookieContainer _cookieJar; 52 | private CacheService _cacheService; 53 | 54 | private Dictionary _restClients; 55 | private Dictionary _restCdnClients; 56 | 57 | private string _dbdVersion; 58 | 59 | public DdbService( 60 | ILogger logger, 61 | CacheService cacheService, 62 | IConfiguration config 63 | ) 64 | { 65 | _cacheService = cacheService; 66 | _logger = logger; 67 | _config = config; 68 | 69 | _dbdVersion = null; 70 | _cookieJar = new CookieContainer(); 71 | 72 | _restClients = new Dictionary(); 73 | _restCdnClients = new Dictionary(); 74 | 75 | // cached item info 76 | MapInfos = new ConcurrentDictionary>(); 77 | PerkInfos = new ConcurrentDictionary>(); 78 | OfferingInfos = new ConcurrentDictionary>(); 79 | CustomItemInfos = new ConcurrentDictionary>(); 80 | CharacterInfos = new ConcurrentDictionary>(); 81 | ItemAddonInfos = new ConcurrentDictionary>(); 82 | ItemInfos = new ConcurrentDictionary>(); 83 | TunableInfos = new ConcurrentDictionary(); 84 | 85 | 86 | foreach (var api in AllowedPrefixes) 87 | { 88 | _restClients[api] = CreateDBDRestClient($"steam.{api}.bhvrdbd.com"); 89 | _restCdnClients[api] = CreateDBDRestClient($"cdn.{api}.dbd.bhvronline.com"); 90 | } 91 | } 92 | 93 | private RestClient CreateDBDRestClient(string baseUrl) 94 | => new RestClient($"https://{baseUrl}") 95 | { 96 | UserAgent = UserAgent, 97 | CookieContainer = _cookieJar 98 | }; 99 | 100 | private static string InvalidResponseJson(string reason) 101 | { 102 | try 103 | { 104 | return JsonConvert.SerializeObject(new { error = reason, success = "false" }); 105 | } catch (Exception e) 106 | { 107 | return string.Format("Json Serailize Error: {0}", e.Message); 108 | } 109 | } 110 | 111 | 112 | 113 | private async Task LazyHttpRequest(RestRequest request, string branch, bool cache = true, 114 | int ttl = 1800) 115 | { 116 | 117 | var retries = 0; 118 | IRestResponse response = null; 119 | 120 | if (cache) 121 | { 122 | var cacheResult = await _cacheService.GetCachedRequest(request, branch); 123 | if (cacheResult != null) return cacheResult; 124 | } 125 | 126 | while (true) 127 | { 128 | if (retries > 3) 129 | break; 130 | 131 | response = await _restClients[branch].ExecuteTaskAsync(request); 132 | if (response.StatusCode == HttpStatusCode.Forbidden) 133 | await UpdateSessionToken(branch); 134 | else 135 | break; 136 | 137 | retries += 1; 138 | } 139 | 140 | if (cache) 141 | await _cacheService.CacheRequest(request, response, branch, ttl); 142 | 143 | #if DEBUG 144 | if (response != null) 145 | { 146 | _logger.LogDebug("Request: {0} {1} -> {2}", request.Method.ToString(), 147 | request.Resource, response.StatusCode); 148 | 149 | if(response.StatusCode != HttpStatusCode.OK) 150 | { 151 | _logger.LogError("Response (NOT OK) ->\n{0}", response.Content); 152 | } 153 | } 154 | #endif 155 | 156 | return response; 157 | } 158 | 159 | private string UpdateDbdVersion(string branch = "live", string config = "", bool fromMainConfig = true) 160 | { 161 | if (string.IsNullOrEmpty(config)) 162 | return null; 163 | 164 | try 165 | { 166 | JObject versions; 167 | 168 | if (fromMainConfig) 169 | { 170 | var configData = JArray.Parse(config); 171 | versions = configData.Children() 172 | .FirstOrDefault(x => x.GetValue("keyName").ToString() == "VER_CLIENT_DATA"); 173 | } 174 | else 175 | { 176 | var versionData = JObject.Parse(config); 177 | versions = (JObject)versionData["availableVersions"]; 178 | } 179 | 180 | if (versions == null || versions.Equals(default)) 181 | return null; 182 | 183 | 184 | var version = ""; 185 | foreach (KeyValuePair versionKV in fromMainConfig ? (JObject)versions["value"] : versions) 186 | { 187 | if (versionKV.Key.StartsWith("m_")) 188 | break; 189 | 190 | version = versionKV.Key; 191 | } 192 | 193 | if(fromMainConfig) 194 | _dbdVersion = version; 195 | 196 | return version; 197 | } 198 | catch 199 | { 200 | // failed 201 | } 202 | 203 | return null; 204 | } 205 | 206 | /* 207 | private async Task GetSteamSessionToken() 208 | { 209 | if (!_steamService.Connected) return ""; 210 | 211 | // dead by daylight app 212 | byte[] buffer = await _steamService.GetAuthSessionTicket(new GameID(381210)); 213 | var token = BitConverter.ToString(buffer, 0, (int)buffer.Length); 214 | Console.WriteLine("token {0}", token.Replace("-", string.Empty)); 215 | return token.Replace("-", string.Empty); 216 | } 217 | */ 218 | 219 | public async Task GetCurrentDBDVersion(string branch = "live") 220 | { 221 | RestClient client; 222 | if (!_restClients.TryGetValue(branch, out client)) 223 | return null; 224 | 225 | var verRequest = new RestRequest("api/v1/utils/contentVersion/version", Method.GET); 226 | verRequest.AddHeader("User-Agent", UserAgent); 227 | 228 | var verResponse = await client.ExecuteGetTaskAsync(verRequest); 229 | if (verResponse.StatusCode != HttpStatusCode.OK) 230 | return null; 231 | 232 | return UpdateDbdVersion(branch, verResponse.Content, false); 233 | } 234 | 235 | public async Task UpdateSessionToken(string branch = "live") 236 | { 237 | //var token = await GetSteamSessionToken(); 238 | //if (string.IsNullOrEmpty(token)) return false; 239 | if (!_restClients.ContainsKey(branch)) 240 | return false; 241 | 242 | var version = await GetCurrentDBDVersion(branch); 243 | if (string.IsNullOrEmpty(version)) 244 | return false; 245 | 246 | 247 | var request = new RestRequest("api/v1/auth/login/guest", Method.POST); 248 | //var request = new RestRequest("api/v1/auth/provider/steam/login"); 249 | // request.AddQueryParameter("token", token); 250 | request.AddJsonBody(new 251 | { 252 | clientData = new 253 | { 254 | catalogId = version, 255 | gameContentId = version, 256 | consentId = version 257 | } 258 | }); 259 | 260 | var response = await _restClients[branch].ExecutePostTaskAsync(request); 261 | 262 | if (response.StatusCode == HttpStatusCode.OK) 263 | foreach(var cookie in response.Cookies) 264 | { 265 | if (cookie.Name != "bhvrSession" || string.IsNullOrEmpty(cookie.Value)) continue; 266 | _cookieJar.Add(new Cookie(cookie.Name, cookie.Value) 267 | { 268 | Domain = cookie.Domain 269 | }); 270 | 271 | return true; 272 | } 273 | 274 | 275 | return false; 276 | } 277 | 278 | 279 | public async Task GetShrine(string branch = "live") 280 | { 281 | if (!_restClients.ContainsKey(branch)) 282 | return null; 283 | 284 | var restRequest = new RestRequest("api/v1/extensions/shrine/getAvailable", Method.POST); 285 | restRequest.AddJsonBody(new {data = new {version = "steam"}}); 286 | 287 | var response = await LazyHttpRequest(restRequest, branch, false) ; 288 | var body = Encoding.ASCII.GetString(response.RawBytes); 289 | return response.StatusCode == HttpStatusCode.OK ? ShrineConvert.FromJson(body) : null; 290 | } 291 | 292 | public async Task GetStoreOutfits(string branch = "live") 293 | { 294 | if (!_restClients.ContainsKey(branch)) 295 | return null; 296 | 297 | var restRequest = new RestRequest("api/v1/extensions/store/getOutfits", Method.POST); 298 | restRequest.AddJsonBody(new 299 | { 300 | data = new { } 301 | }); 302 | 303 | var response = await LazyHttpRequest(restRequest, branch); 304 | var body = Encoding.ASCII.GetString(response.RawBytes); 305 | return response.StatusCode == HttpStatusCode.OK ? StoreConvert.FromJson(body) : null; 306 | } 307 | 308 | public async Task GetApiConfig(string branch = "live") 309 | { 310 | if (!_restClients.ContainsKey(branch)) 311 | return InvalidResponseJson("Disallowed api branch!"); 312 | 313 | var restRequest = new RestRequest("api/v1/config", Method.GET); 314 | 315 | var response = await LazyHttpRequest(restRequest, branch, false); 316 | var responseBody = response.StatusCode == HttpStatusCode.OK ? 317 | Encoding.ASCII.GetString(response.RawBytes) : InvalidResponseJson("Invalid response from DBD Api"); 318 | 319 | if (branch == "live" && response.StatusCode == HttpStatusCode.OK) 320 | UpdateDbdVersion(branch, responseBody); 321 | 322 | return responseBody; 323 | } 324 | 325 | public async Task GetCdnContentFormat(string uriFormat, string cdnPrefix, bool cache = true) 326 | { 327 | if (_dbdVersion == null && (await GetApiConfig()).Contains("Invalid response from DBD Api")) 328 | return InvalidResponseJson("Failed to get DBD version"); 329 | 330 | return await GetCdnContent(string.Format(uriFormat, _dbdVersion), cdnPrefix, cache); 331 | } 332 | 333 | public async Task GetCdnContent(string uri, string cdnPrefix = "live", bool cache = true) 334 | { 335 | if (string.IsNullOrEmpty(_config["dbd_decrypt_key"])) 336 | return InvalidResponseJson("Invalid decryption key!"); 337 | 338 | if (!_restCdnClients.ContainsKey(cdnPrefix)) 339 | return InvalidResponseJson("Disallowed cdn branch!"); 340 | 341 | var request = new RestRequest(uri); 342 | IRestResponse response; 343 | 344 | if (cache) 345 | { 346 | response = await _cacheService.GetCachedRequest(request, cdnPrefix); 347 | if (response != null) goto skipOver; 348 | } 349 | 350 | response = await _restCdnClients[cdnPrefix].ExecuteGetTaskAsync(request); 351 | if (cache) await _cacheService.CacheRequest(request, response, cdnPrefix); 352 | 353 | skipOver: 354 | if (response == null || !response.IsSuccessful) 355 | return InvalidResponseJson("Invalid response from DBD CDN"); 356 | 357 | var body = Encoding.UTF8.GetString(response.RawBytes, 0, (int)response.ContentLength); 358 | 359 | return response.StatusCode != HttpStatusCode.OK ? "" : Encryption.DecryptCdn(body, _config["dbd_decrypt_key"]); 360 | } 361 | 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /DBD-API/Services/SteamDepotService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text.RegularExpressions; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using DBD_API.Modules.DbD; 10 | using DBD_API.Modules.DbD.PakItems; 11 | using Microsoft.Extensions.Hosting; 12 | 13 | using DBD_API.Modules.Steam; 14 | using DBD_API.Modules.Steam.ContentManagement; 15 | using Microsoft.AspNetCore.Mvc.Diagnostics; 16 | using Microsoft.Extensions.Logging; 17 | using SteamKit2; 18 | using SteamKit2.Unified.Internal; 19 | using UETools.Assets; 20 | using UETools.Core.Enums; 21 | using UETools.Core.Interfaces; 22 | using UETools.Objects.Classes; 23 | using UETools.Objects.Structures; 24 | using UETools.Pak; 25 | 26 | using CustomItemInfo = DBD_API.Modules.DbD.PakItems.CustomItemInfo; 27 | using LocalizationTable = UETools.Core.Interfaces.IUnrealLocalizationProvider; 28 | 29 | namespace DBD_API.Services 30 | { 31 | public class SteamDepotService : IHostedService 32 | { 33 | private static string Deliminator = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/" : "\\"; 34 | 35 | private static readonly Regex[] FileDownloadList = 36 | { 37 | new Regex(@"(Paks\" + Deliminator + @"pakchunk0\-WindowsNoEditor\.pak)$"), 38 | new Regex(@"(UI\" + Deliminator + @".*?\.png)$"), 39 | }; 40 | 41 | private static readonly object[] AppDownloadList = 42 | { 43 | new { Branch = "Public", AppId = 381210 }, 44 | }; 45 | 46 | 47 | private static Regex _tunableRegexFix = new Regex(@"(:?.\w+TunableDB$)"); 48 | 49 | private CancellationTokenSource _cancellation; 50 | private ContentDownloader _contentDownloader; 51 | private CDNClientPool _cdnClientPool; 52 | 53 | private ILogger _logger; 54 | private uint _lastChangeNumber; 55 | 56 | private readonly DdbService _dbdService; 57 | private readonly SteamService _steamService; 58 | private readonly CallbackManager _callbackManager; 59 | 60 | public SteamDepotService(SteamService steamService, DdbService dbdService, ILogger logger) 61 | { 62 | _dbdService = dbdService; 63 | _steamService = steamService; 64 | 65 | _callbackManager = new CallbackManager(_steamService.Client); 66 | _callbackManager.Subscribe(OnPICSChanges); 67 | 68 | _lastChangeNumber = 0; 69 | _logger = logger; 70 | } 71 | 72 | private async Task ReadLocalization(PakVFS pakReader) 73 | { 74 | var localizationTable = pakReader.AbsoluteIndex.FirstOrDefault(x => x.Key.Contains("/en/") && x.Key.EndsWith(".locres")); 75 | if (localizationTable.Equals(default)) 76 | return null; 77 | 78 | await using var data = await localizationTable.Value.ReadAsync(); 79 | data.Version = UE4Version.VER_UE4_AUTOMATIC_VERSION; 80 | LocResAsset.English.Serialize(data); 81 | return LocResAsset.English; 82 | 83 | } 84 | 85 | private async Task LoadDBFromPak(PakVFS pakReader, LocalizationTable localization, ConcurrentDictionary itemDB, string dbName, string typeName = "", 86 | Func onRowRead = null) 87 | { 88 | if (string.IsNullOrEmpty(typeName)) 89 | typeName = dbName; 90 | 91 | foreach (var (_, db) in pakReader.AbsoluteIndex.Where(x => x.Key.EndsWith($"{dbName}.uasset"))) 92 | await using (var data = await db.ReadAsync()) 93 | { 94 | data.Version = UE4Version.VER_UE4_AUTOMATIC_VERSION; 95 | data.Localization = localization; 96 | UAssetAsset asset = null; 97 | data.Read(ref asset); 98 | 99 | var dataTable = asset.GetExportsOfType() 100 | .FirstOrDefault(); 101 | 102 | if (!(dataTable is DataTable items)) 103 | continue; 104 | 105 | foreach (var (itemName, itemInfo) in items.Rows) 106 | { 107 | var item = (T) Activator.CreateInstance(typeof(T), itemInfo.Vars); 108 | itemDB[itemName] = item; 109 | 110 | if(onRowRead != null) 111 | await onRowRead.Invoke(itemInfo, item); 112 | } 113 | } 114 | } 115 | 116 | private async Task ReadMapInfo(PakVFS pakReader, LocalizationTable localization, string branch) 117 | { 118 | if (!_dbdService.MapInfos.TryGetValue(branch, out var mapInfos)) 119 | { 120 | mapInfos = new ConcurrentDictionary(); 121 | _dbdService.MapInfos[branch] = mapInfos; 122 | } 123 | 124 | await LoadDBFromPak(pakReader, localization, mapInfos, "ProceduralMaps"); 125 | } 126 | 127 | private async Task ReadPerkInfo(PakVFS pakReader, LocalizationTable localization, string branch) 128 | { 129 | if (!_dbdService.PerkInfos.TryGetValue(branch, out var perkInfos)) 130 | { 131 | perkInfos = new ConcurrentDictionary(); 132 | _dbdService.PerkInfos[branch] = perkInfos; 133 | } 134 | 135 | await LoadDBFromPak(pakReader, localization, perkInfos, "PerkDB"); 136 | } 137 | 138 | private async Task ReadOfferingInfo(PakVFS pakReader, LocalizationTable localization, string branch) 139 | { 140 | if (!_dbdService.OfferingInfos.TryGetValue(branch, out var offeringInfos)) 141 | { 142 | offeringInfos = new ConcurrentDictionary(); 143 | _dbdService.OfferingInfos[branch] = offeringInfos; 144 | } 145 | 146 | await LoadDBFromPak(pakReader, localization, offeringInfos, "OfferingDB"); 147 | } 148 | 149 | private async Task ReadItemInfo(PakVFS pakReader, LocalizationTable localization, string branch) 150 | { 151 | if (!_dbdService.ItemInfos.TryGetValue(branch, out var itemInfos)) 152 | { 153 | itemInfos = new ConcurrentDictionary(); 154 | _dbdService.ItemInfos[branch] = itemInfos; 155 | } 156 | 157 | await LoadDBFromPak(pakReader, localization, itemInfos, "ItemDB"); 158 | } 159 | 160 | private async Task ReadItemAddonInfo(PakVFS pakReader, LocalizationTable localization, string branch) 161 | { 162 | if (!_dbdService.ItemAddonInfos.TryGetValue(branch, out var itemInfos)) 163 | { 164 | itemInfos = new ConcurrentDictionary(); 165 | _dbdService.ItemAddonInfos[branch] = itemInfos; 166 | } 167 | 168 | await LoadDBFromPak(pakReader, localization, itemInfos, "ItemAddonDB"); 169 | } 170 | 171 | private async Task ReadCustomItemInfo(PakVFS pakReader, LocalizationTable localization, string branch) 172 | { 173 | if (!_dbdService.CustomItemInfos.TryGetValue(branch, out var itemInfos)) 174 | { 175 | itemInfos = new ConcurrentDictionary(); 176 | _dbdService.CustomItemInfos[branch] = itemInfos; 177 | } 178 | 179 | await LoadDBFromPak(pakReader, localization, itemInfos, "CustomizationItemDB"); 180 | } 181 | 182 | private async Task ReadTunableInfo(PakVFS pakReader, LocalizationTable localization, string branch, string name, string tunable, 183 | ConcurrentDictionary tunableInfos) 184 | { 185 | tunable = _tunableRegexFix.Replace(tunable, ".uasset"); 186 | tunable = tunable.Replace("/Game/", "DeadByDaylight/Content/"); 187 | 188 | var pakPath = pakReader.AbsoluteIndex.FirstOrDefault(x => x.Key == tunable); 189 | if (pakPath.Equals(default) || pakPath.Value == null) 190 | return; 191 | 192 | 193 | await using (var data = await pakPath.Value.ReadAsync()) 194 | { 195 | data.Version = UE4Version.VER_UE4_AUTOMATIC_VERSION; 196 | data.Localization = localization; 197 | UAssetAsset asset = null; 198 | data.Read(ref asset); 199 | 200 | var dataTable = asset.GetExportsOfType() 201 | .FirstOrDefault(); 202 | 203 | if (!(dataTable is DataTable items)) 204 | return; 205 | 206 | tunableInfos[name] = new TunableInfo(items.Rows); 207 | } 208 | } 209 | 210 | private async Task ReadTunablesInfo(PakVFS pakReader, LocalizationTable localization, string branch) 211 | { 212 | if (!_dbdService.CharacterInfos.TryGetValue(branch, out var characterInfos)) 213 | return; 214 | 215 | if (!_dbdService.TunableInfos.TryGetValue(branch, out var tunableInfos)) 216 | { 217 | tunableInfos = new TunableContainer(); 218 | _dbdService.TunableInfos[branch] = tunableInfos; 219 | } 220 | 221 | var miscTunables = pakReader.AbsoluteIndex.FirstOrDefault(x => x.Key.EndsWith("KillerTunableDB.uasset")); 222 | if (!miscTunables.Equals(default)) 223 | await ReadTunableInfo(pakReader, localization, branch, "Killer", miscTunables.Key, 224 | tunableInfos.BaseTunables); 225 | 226 | miscTunables = pakReader.AbsoluteIndex.FirstOrDefault(x => x.Key.EndsWith("SurvivorTunableDB.uasset")); 227 | if (!miscTunables.Equals(default)) 228 | await ReadTunableInfo(pakReader, localization, branch, "Survivor", miscTunables.Key, 229 | tunableInfos.BaseTunables); 230 | 231 | 232 | var regex = new Regex(@"^(\/Game\/Data\/Dlc\/\w+\/).*?$"); 233 | var otherRegex = new Regex(@"^.*Dlc\/(.*?)\/TunableValuesDB\.uasset$"); 234 | 235 | var matchedTunableValues = new List(); 236 | 237 | foreach (var characterTunable in characterInfos.Where(x => x.Value.TunablePath != "None")) 238 | { 239 | var character = characterTunable.Value; 240 | var match = regex.Match(character.TunablePath); 241 | await ReadTunableInfo(pakReader, localization, branch, character.IdName, character.TunablePath, 242 | tunableInfos.KillerTunables); 243 | 244 | if (!match.Success) 245 | continue; 246 | 247 | var matchPath = match.Groups[1].Value.Replace("/Game/", "DeadByDaylight/Content/") 248 | + "TunableValuesDB.uasset"; 249 | var tunableValueKv = pakReader.AbsoluteIndex.FirstOrDefault(x => 250 | x.Key == matchPath); 251 | if (tunableValueKv.Key == null) 252 | continue; 253 | 254 | await ReadTunableInfo(pakReader, localization, branch, character.IdName, tunableValueKv.Key, 255 | tunableInfos.KnownTunableValues); 256 | 257 | matchedTunableValues.Add(tunableValueKv.Key); 258 | } 259 | 260 | foreach (var tunableValues in pakReader.AbsoluteIndex.Where(x => x.Key.EndsWith("TunableValuesDB.uasset") && 261 | !matchedTunableValues.Contains(x.Key))) 262 | { 263 | var path = tunableValues.Key; 264 | var name = otherRegex.Match(path); 265 | if (!name.Success) 266 | continue; 267 | 268 | await ReadTunableInfo(pakReader, localization, branch, $"{name.Groups[1].Value}TunableValues", path, 269 | tunableInfos.UnknownTunableValues); 270 | } 271 | } 272 | 273 | private async Task ReadCharacterInfo(PakVFS pakReader, LocalizationTable localization, string branch) 274 | { 275 | if (!_dbdService.CharacterInfos.TryGetValue(branch, out var characterInfos)) 276 | { 277 | characterInfos = new ConcurrentDictionary(); 278 | _dbdService.CharacterInfos[branch] = characterInfos; 279 | } 280 | 281 | await LoadDBFromPak(pakReader, localization, characterInfos, "CharacterDescriptionDB"); 282 | 283 | /*await LoadDBFromPak(pakReader, localization, characterInfos, "CharacterDescriptionDB", "", 284 | async (obj, parsedObj) => 285 | { 286 | var tunableKv = obj.Vars.FirstOrDefault(x => x.Key == "TunableDB"); 287 | if (tunableKv.Equals(default) || !(tunableKv.Value.Value is TaggedObject tunable)) 288 | return; 289 | 290 | var ptrKv = tunable.Vars.FirstOrDefault(x => x.Key == "AssetPtr"); 291 | if (ptrKv.Equals(default) || !(ptrKv.Value.Value is var softObj)) 292 | return; 293 | 294 | var path = softObj.ToString(); 295 | if (path != "None" && !string.IsNullOrEmpty(parsedObj.IdName)) 296 | await ReadTunablesInfo(pakReader, localization, branch, parsedObj.IdName, path); 297 | }); 298 | */ 299 | 300 | } 301 | 302 | // TODO: implement 303 | private async void OnPICSChanges(SteamApps.PICSChangesCallback callback) 304 | { 305 | if (callback.CurrentChangeNumber == _lastChangeNumber) 306 | return; 307 | 308 | foreach (var (key, _) in callback.AppChanges) 309 | { 310 | var appInList = AppDownloadList.Where(x => ((dynamic) x).AppId == key); 311 | foreach (var subApp in appInList) 312 | await DownloadAppAsync(key, ((dynamic) subApp).Branch); 313 | } 314 | } 315 | 316 | 317 | private async Task DownloadAppAsync(uint appId, string branch) 318 | { 319 | try 320 | { 321 | await _contentDownloader.DownloadFilesAsync(appId, branch, 322 | FileDownloadList.ToList(), 323 | _cancellation.Token); 324 | } 325 | catch (Exception ex) 326 | { 327 | _logger.LogError("Failed to download app {1} '{0}': {2}", branch, appId, ex.Message); 328 | return; 329 | } 330 | 331 | try 332 | { 333 | var pakReader = await PakVFS.OpenAtAsync($"data/{appId}/{branch}/Paks"); 334 | var localization = await ReadLocalization(pakReader); 335 | 336 | await ReadMapInfo(pakReader, localization, branch); 337 | await ReadPerkInfo(pakReader, localization, branch); 338 | await ReadOfferingInfo(pakReader, localization, branch); 339 | await ReadCustomItemInfo(pakReader, localization, branch); 340 | await ReadCharacterInfo(pakReader, localization, branch); 341 | await ReadItemAddonInfo(pakReader, localization, branch); 342 | await ReadItemInfo(pakReader, localization, branch); 343 | await ReadTunablesInfo(pakReader, localization, branch); 344 | } 345 | catch (Exception ex) 346 | { 347 | _logger.LogError("Failed to read paks for app {1} '{0}': {2}", branch, appId, ex.Message); 348 | _logger.LogTrace("Stacktrace: {0}", ex.StackTrace); 349 | } 350 | } 351 | 352 | public async Task MainEvent() 353 | { 354 | if (!_steamService.LicenseCompletionSource.Task.IsCompleted) 355 | await _steamService.LicenseCompletionSource.Task; 356 | 357 | _logger.LogInformation("Got {0} licenses", _steamService.Licenses.Count); 358 | 359 | try 360 | { 361 | foreach (dynamic app in AppDownloadList) 362 | await DownloadAppAsync((uint) app.AppId, (string) app.Branch); 363 | 364 | } 365 | catch (Exception ex) 366 | { 367 | _logger.LogError("Failed to download app: {0}", ex); 368 | } 369 | 370 | } 371 | 372 | public Task StartAsync(CancellationToken cancellationToken) 373 | { 374 | _cancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 375 | _cdnClientPool = new CDNClientPool(_steamService.Client, _cancellation); 376 | _contentDownloader = new ContentDownloader(_steamService.Client, _cdnClientPool, _steamService.Licenses, _logger); 377 | 378 | var task = Task.Factory.StartNew(MainEvent, _cancellation.Token, TaskCreationOptions.LongRunning, 379 | TaskScheduler.Default); 380 | 381 | return task.IsCompleted ? task : Task.CompletedTask; 382 | } 383 | 384 | public Task StopAsync(CancellationToken cancellationToken) 385 | { 386 | _cancellation?.Cancel(); 387 | return Task.CompletedTask; 388 | } 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /DBD-API/Services/SteamEventService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace DBD_API.Services 10 | { 11 | public class SteamEventService : IHostedService 12 | { 13 | private Task _eventLoop; 14 | private CancellationTokenSource _cancelEventLoop; 15 | private readonly SteamService _steamService; 16 | private readonly ILogger _logger; 17 | 18 | public SteamEventService( 19 | SteamService steamService, 20 | ILogger logger 21 | ) 22 | { 23 | _steamService = steamService; 24 | _logger = logger; 25 | } 26 | 27 | // utter cancer lol 28 | public Task StartAsync(CancellationToken cancellationToken) 29 | { 30 | _cancelEventLoop = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 31 | 32 | _steamService.Client.Connect(); 33 | //eventLoop = RunEventLoop(cancelEventLoop.Token); 34 | _eventLoop = Task.Factory.StartNew(() => 35 | { 36 | _logger.LogInformation("event-loop start!"); 37 | while (!_cancelEventLoop.Token.IsCancellationRequested && _steamService.KeepAlive) 38 | _steamService.Manager.RunWaitCallbacks(); 39 | }, _cancelEventLoop.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 40 | 41 | return _eventLoop.IsCompleted ? _eventLoop : Task.CompletedTask; 42 | } 43 | 44 | public async Task StopAsync(CancellationToken cancellationToken) 45 | { 46 | _logger.LogInformation("Stopping service..."); 47 | if (_steamService.KeepAlive && _eventLoop != null) 48 | { 49 | _steamService.KeepAlive = false; 50 | _steamService.Connected = false; 51 | 52 | _cancelEventLoop.Cancel(); 53 | await Task.WhenAny(_eventLoop, Task.Delay(-1, cancellationToken)); 54 | cancellationToken.ThrowIfCancellationRequested(); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /DBD-API/Services/SteamService.cs: -------------------------------------------------------------------------------- 1 | // credit: jesterret (b1g credit) 2 | // saved me a bunch of money with steamkit suggestion + example :) 3 | 4 | using DBD_API.Modules.Steam; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Logging; 7 | using SteamKit2; 8 | using SteamKit2.Internal; 9 | using System; 10 | using System.Collections.Concurrent; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Security.Cryptography; 15 | using System.Threading.Tasks; 16 | using EResult = SteamKit2.EResult; 17 | using LicenseList = System.Collections.Generic.List; 18 | using SteamApps = SteamKit2.SteamApps; 19 | using SteamClient = SteamKit2.SteamClient; 20 | using SteamUser = SteamKit2.SteamUser; 21 | 22 | 23 | namespace DBD_API.Services 24 | { 25 | public class SteamService 26 | { 27 | private readonly IConfiguration _config; 28 | private readonly ILogger _logger; 29 | 30 | public readonly TaskCompletionSource LicenseCompletionSource; 31 | public LicenseList Licenses; 32 | public bool Connected; 33 | public bool KeepAlive; 34 | 35 | // token grabbing 36 | private ConcurrentQueue _gcTokens; 37 | private TaskCompletionSource _gcTokensComplete; 38 | private ConcurrentDictionary> _gameTickets; 39 | 40 | // steamkit required 41 | public CallbackManager Manager; 42 | public SteamClient Client; 43 | private SteamUser _user; 44 | private SteamApps _apps; 45 | private SteamUserStats _userStats; 46 | 47 | public SteamService(IConfiguration config, ILogger logger) 48 | { 49 | Connected = false; 50 | KeepAlive = true; 51 | _config = config; 52 | _logger = logger; 53 | 54 | _gcTokens = new ConcurrentQueue(); 55 | _gcTokensComplete = new TaskCompletionSource(); 56 | _gameTickets = new ConcurrentDictionary>(); 57 | 58 | Licenses = new LicenseList(); 59 | LicenseCompletionSource = new TaskCompletionSource(false); 60 | 61 | InitializeClient(); 62 | } 63 | 64 | private void InitializeClient() 65 | { 66 | // client/manager creation 67 | Client = new SteamClient(); 68 | Manager = new CallbackManager(Client); 69 | 70 | _user = Client.GetHandler(); 71 | _apps = Client.GetHandler(); 72 | _userStats = Client.GetHandler(); 73 | 74 | Client.AddHandler(new SteamTicketAuth()); 75 | Client.AddHandler(new SteamStatsHandler()); 76 | 77 | // subscriptions 78 | Manager.Subscribe(OnDisconnected); 79 | Manager.Subscribe(OnConnected); 80 | Manager.Subscribe(OnMachineAuth); 81 | Manager.Subscribe(OnLoggedOn); 82 | Manager.Subscribe(OnLicenseList); 83 | 84 | // internal subs 85 | Manager.Subscribe(OnGcTokens); 86 | } 87 | 88 | // methods 89 | public async Task GetAuthSessionTicket(GameID gameId) 90 | { 91 | if (!Connected) 92 | throw new Exception("Not connected to stream"); 93 | 94 | await _gcTokensComplete.Task; 95 | if (!_gcTokens.TryDequeue(out var token)) 96 | throw new Exception("Failed to get gc token"); 97 | 98 | var ticket = new MemoryStream(); 99 | var writer = new BinaryWriter(ticket); 100 | 101 | CreateAuthTicket(token, writer); 102 | 103 | var verifyTask = VerifyTicket(gameId.AppID, ticket, out uint crc).ToTask(); 104 | var appTicketTask = _apps.GetAppOwnershipTicket(gameId.AppID).ToTask(); 105 | await Task.WhenAll(verifyTask, appTicketTask); 106 | 107 | var callback = await verifyTask; 108 | if (callback.ActiveTicketsCRC.All(x => x != crc)) 109 | throw new Exception("Failed verify active tickets"); 110 | 111 | var appTicket = await appTicketTask; 112 | if (appTicket.Result != EResult.OK) 113 | throw new Exception("Failed to get ownership ticket"); 114 | 115 | writer.Write(appTicket.Ticket.Length); 116 | writer.Write(appTicket.Ticket); 117 | return ticket.ToArray(); 118 | 119 | } 120 | 121 | 122 | // TODO: implement me 123 | public async Task GetUserAchievements(GameID gameId, SteamID steamId) 124 | { 125 | var response = await RequestUserStats(gameId, steamId); 126 | //if (response.Result != EResult.OK || response.AchievementBlocks == null) 127 | //return null; 128 | 129 | //return response.AchievementBlocks; 130 | } 131 | 132 | public async Task> GetUserStats(GameID gameId, SteamID steamId) 133 | { 134 | var response = await RequestUserStats(gameId, steamId); 135 | if (response.Result != EResult.OK || response.Schema == null) 136 | return null; 137 | 138 | var results = new Dictionary(); 139 | var schema = response.ParsedSchema; 140 | var stats = schema.Children.FirstOrDefault(x => x.Name == "stats"); 141 | if (stats == null || stats.Equals(default(Dictionary))) 142 | goto exit; 143 | 144 | foreach (var stat in stats.Children) 145 | { 146 | var statName = stat.Children.FirstOrDefault(x => x.Name == "name"); 147 | if (statName == null || statName.Equals(default(KeyValue))) 148 | continue; 149 | 150 | 151 | var statValue = response.Stats.FirstOrDefault(x => Convert.ToString(x.stat_id) == stat.Name); 152 | if (statValue == null || statValue.Equals(default(CMsgClientGetUserStatsResponse.Stats))) 153 | continue; 154 | 155 | if(statName.Value.EndsWith("_float")) 156 | results.TryAdd(statName.Value, BitConverter.ToSingle(BitConverter.GetBytes(statValue.stat_value))); 157 | else 158 | results.TryAdd(statName.Value, statValue.stat_value); 159 | } 160 | 161 | exit: 162 | return results; 163 | } 164 | 165 | // internals 166 | private void CreateAuthTicket(byte[] gcToken, BinaryWriter stream) 167 | { 168 | // gc token 169 | stream.Write(gcToken.Length); 170 | stream.Write(gcToken.ToArray()); 171 | 172 | // session 173 | stream.Write(24); // length 174 | stream.Write(1); // unk 1 175 | stream.Write(2); // unk 2 176 | stream.Write(0); // pub ip addr 177 | stream.Write(0); // padding 178 | stream.Write(2038); // ms connected 179 | stream.Write(1); // connection count 180 | } 181 | 182 | private AsyncJob VerifyTicket(uint appId, MemoryStream stream, out uint crc) 183 | { 184 | var authTicket = stream.ToArray(); 185 | crc = BitConverter.ToUInt32(CryptoHelper.CRCHash(authTicket)); 186 | var items = _gameTickets.GetOrAdd(appId, new List()); 187 | items.Add(new CMsgAuthTicket() 188 | { 189 | gameid = appId, 190 | ticket = authTicket, 191 | ticket_crc = crc 192 | }); 193 | 194 | var authList = new ClientMsgProtobuf(EMsg.ClientAuthList); 195 | authList.Body.tokens_left = (uint)_gcTokens.Count; 196 | authList.Body.app_ids.AddRange(_gameTickets.Keys); 197 | authList.Body.tickets.AddRange(_gameTickets.Values.SelectMany(x => x)); 198 | authList.SourceJobID = Client.GetNextJobID(); 199 | Client.Send(authList); 200 | 201 | return new AsyncJob(Client, authList.SourceJobID); 202 | } 203 | 204 | private AsyncJob RequestUserStats(GameID appId, SteamID steamId) 205 | { 206 | var getUserStats = new ClientMsgProtobuf(EMsg.ClientGetUserStats); 207 | getUserStats.Body.game_id = appId; 208 | getUserStats.Body.game_idSpecified = true; 209 | getUserStats.Body.steam_id_for_user = steamId; 210 | getUserStats.Body.steam_id_for_userSpecified = true; 211 | 212 | getUserStats.SourceJobID = Client.GetNextJobID(); 213 | Client.Send(getUserStats); 214 | 215 | return new AsyncJob(Client, getUserStats.SourceJobID); 216 | } 217 | 218 | // events 219 | private void OnGcTokens(SteamApps.GameConnectTokensCallback obj) 220 | { 221 | foreach (var token in obj.Tokens) _gcTokens.Enqueue(token); 222 | while (_gcTokens.Count > obj.TokensToKeep) _gcTokens.TryDequeue(out _); 223 | _gcTokensComplete.TrySetResult(true); 224 | } 225 | 226 | private void OnLoggedOn(SteamUser.LoggedOnCallback obj) 227 | { 228 | if (obj.Result != EResult.OK) 229 | { 230 | _logger.LogError("Login denied, reason: {0}", obj.Result); 231 | Client.Disconnect(); 232 | return; 233 | } 234 | 235 | Connected = true; 236 | _logger.LogInformation("We are connected! (user={0})", _config["steam_user"]); 237 | } 238 | 239 | public void OnMachineAuth(SteamUser.UpdateMachineAuthCallback obj) 240 | { 241 | _logger.LogInformation("Writing sentry-file..."); 242 | int sentrySize; 243 | byte[] sentryHash; 244 | 245 | 246 | using (var fs = File.Open("sentry.bin", FileMode.OpenOrCreate, FileAccess.ReadWrite)) 247 | { 248 | fs.Seek(obj.Offset, SeekOrigin.Begin); 249 | fs.Write(obj.Data, 0, obj.BytesToWrite); 250 | sentrySize = (int)fs.Length; 251 | fs.Seek(0, SeekOrigin.Begin); 252 | using (var sha = SHA1.Create()) 253 | sentryHash = sha.ComputeHash(fs); 254 | } 255 | 256 | _user.SendMachineAuthResponse(new SteamUser.MachineAuthDetails() 257 | { 258 | JobID = obj.JobID, 259 | FileName = obj.FileName, 260 | BytesWritten = obj.BytesToWrite, 261 | FileSize = sentrySize, 262 | Offset = obj.Offset, 263 | Result = EResult.OK, 264 | LastError = 0, 265 | OneTimePassword = obj.OneTimePassword, 266 | SentryFileHash = sentryHash 267 | }); 268 | 269 | _logger.LogInformation("Finished writing sentry-file!"); 270 | } 271 | 272 | private void OnConnected(SteamClient.ConnectedCallback obj) 273 | { 274 | _logger.LogInformation("Connected to steam! Logging in as '{0}'...", 275 | _config["steam_user"]); 276 | 277 | byte[] sentryHash = null; 278 | if (File.Exists("sentry.bin")) 279 | sentryHash = CryptoHelper.SHAHash( 280 | File.ReadAllBytes("sentry.bin") 281 | ); 282 | 283 | _user.LogOn(new SteamUser.LogOnDetails() 284 | { 285 | ShouldRememberPassword = true, 286 | Username = _config["steam_user"], 287 | Password = _config["steam_pass"], 288 | TwoFactorCode = _config["steam_mfa"], 289 | AuthCode = _config["steam_oauth"], 290 | SentryFileHash = sentryHash, 291 | }); 292 | } 293 | 294 | private void OnDisconnected(SteamClient.DisconnectedCallback obj) 295 | { 296 | _logger.LogInformation("Disconnected from steam!"); 297 | // Console.WriteLine(Environment.StackTrace); 298 | Connected = false; 299 | 300 | if (!obj.UserInitiated) 301 | Client.Connect(); 302 | else 303 | KeepAlive = false; 304 | } 305 | 306 | 307 | private void OnLicenseList(SteamApps.LicenseListCallback obj) 308 | { 309 | if (obj.Result != EResult.OK) return; 310 | 311 | Licenses.Clear(); 312 | Licenses.AddRange(obj.LicenseList); 313 | 314 | if(!LicenseCompletionSource.Task.IsCompleted) 315 | LicenseCompletionSource.TrySetResult(true); 316 | } 317 | 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /DBD-API/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.Json; 6 | using System.Threading.Tasks; 7 | using DBD_API.Modules; 8 | using DBD_API.Modules.SPA; 9 | using DBD_API.Modules.DbD.PakItems; 10 | using Microsoft.AspNetCore.Builder; 11 | using Microsoft.AspNetCore.Hosting; 12 | using Microsoft.AspNetCore.Http; 13 | using Microsoft.Extensions.DependencyInjection; 14 | 15 | using DBD_API.Services; 16 | using Microsoft.AspNetCore.Http.Features; 17 | using Microsoft.Extensions.Configuration; 18 | using Microsoft.Extensions.FileProviders; 19 | using Microsoft.Extensions.Hosting; 20 | 21 | namespace DBD_API 22 | { 23 | public class Startup 24 | { 25 | private static readonly string _publicDataDir = 26 | Path.Combine(Directory.GetCurrentDirectory(), "data", "381210"); 27 | 28 | public IConfiguration Configuration { get; } 29 | 30 | // Configures the class with the config 31 | public Startup(IConfiguration configuration) 32 | { 33 | Configuration = configuration; 34 | 35 | Action ensureConfig = (name) => 36 | { 37 | if(string.IsNullOrEmpty(configuration[name])) 38 | throw new Exception($"Failed to get setting {name}"); 39 | }; 40 | 41 | ensureConfig("steam_user"); 42 | ensureConfig("steam_pass"); 43 | 44 | if (!Directory.Exists(_publicDataDir)) 45 | Directory.CreateDirectory(_publicDataDir); 46 | } 47 | 48 | public void ConfigureServices(IServiceCollection services) 49 | { 50 | // logging 51 | services.AddLogging(); 52 | services.AddCors(); 53 | 54 | if(!string.IsNullOrEmpty(Configuration["redis:config"])) 55 | services.AddDistributedRedisCache(x => 56 | { 57 | x.Configuration = Configuration["redis:config"]; 58 | x.InstanceName = Configuration["redis:instance"]; 59 | }); 60 | else 61 | services.AddDistributedMemoryCache(); 62 | 63 | // services 64 | services.AddSingleton(); 65 | services.AddSingleton(); 66 | services.AddSingleton(); 67 | services.AddHostedService(); 68 | services.AddHostedService(); 69 | 70 | // mvc 71 | services.AddControllers() 72 | .AddJsonOptions(x => 73 | { 74 | x.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; 75 | x.JsonSerializerOptions.Converters.Add(new TunableSerializer()); 76 | }); 77 | 78 | // spa 79 | services.AddSpaStaticFiles(options => options.RootPath = "ClientApp/dist"); 80 | } 81 | 82 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 83 | { 84 | if (env.IsDevelopment()) 85 | app.UseDeveloperExceptionPage(); 86 | else 87 | app.UseHsts(); 88 | 89 | app.UseHttpsRedirection(); 90 | 91 | app.UseStaticFiles(new StaticFileOptions 92 | { 93 | FileProvider = new PhysicalFileProvider(_publicDataDir), 94 | HttpsCompression = HttpsCompressionMode.Compress, 95 | RequestPath = "/data" 96 | }); 97 | 98 | 99 | app.UseRouting(); 100 | 101 | app.UseCors(builder => 102 | { 103 | builder 104 | .AllowAnyHeader() 105 | .AllowAnyMethod() 106 | .AllowAnyOrigin(); 107 | }); 108 | 109 | app.UseEndpoints(endpoints => 110 | { 111 | endpoints.MapControllers(); 112 | endpoints.MapDefaultControllerRoute(); 113 | }); 114 | 115 | app.UseSpaStaticFiles(); 116 | 117 | app.UseSpa(spa => 118 | { 119 | spa.Options.SourcePath = "ClientApp"; 120 | if (env.IsDevelopment()) 121 | { 122 | // Launch development server for Vue.js 123 | spa.UseVueDevelopmentServer(); 124 | } 125 | }); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /DBD-API/libman.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "defaultProvider": "cdnjs", 4 | "libraries": [] 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jack Fox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DBD-API 2 | Dead By Daylight API 3 | 4 | ## What is this for? 5 | It allows you to communicate with Dead By Daylight's backend, though if you have any suggestions for new things to add to the API, I may add them for you. 6 | 7 | ## What purpose does it serve? 8 | Currently I use it to retreive and display the shrine of secrets and output when the next one is. 9 | 10 | ## How does it work? 11 | It logins in anonymously to the Dead By Daylight API, and retrieves information from endpoints from the api and returns them, additionally it does the same from Dead By Daylight's CDN. It also reads from the PAK file (which is what requires the steam user to download the files), to get information such as perks, offerings, characters, tunables, items, etc... 12 | 13 | ## Where is it hosted? 14 | you can find it [here](https://dbd-stats.info) 15 | 16 | ## Endpoints 17 | 1. /api/maps(?branch=live) 18 | 2. /api/perks(?branch=live) 19 | 3. /api/offerings(?branch=live) 20 | 4. /api/characters(?branch=live) 21 | 5. /api/tunables(?branch=live&killer=) 22 | 6. /api/emblemtunables(?branch=live) 23 | 7. /api/customizationitems(?branch=live) 24 | 8. /api/gameconfigs(?branch=live) 25 | 9. /api/ranksthresholds(?branch=live) 26 | 10. /api/itemaddons(?branch=live) 27 | 11. /api/items(?branch=live) 28 | 12. /api/stats/:steam_64: (Profile needs to be public) 29 | 13. /api/shrineofsecrets(?pretty=true&branch=live) 30 | 14. /api/storeoutfits(?branch=live) 31 | 15. /api/config(?branch=live) 32 | 16. /api/catalog(?branch=live) 33 | 17. /api/news(?branch=live) 34 | 18. /api/featured(?branch=live) 35 | 19. /api/schedule(?branch=live) 36 | 20. /api/bpevents(?branch=live) 37 | 21. /api/specialevents(?branch=live) 38 | 22. /api/archive(?branch=ptb&tome=Tome01) 39 | 23. /api/archiverewarddata(?branch=live) 40 | --------------------------------------------------------------------------------