├── .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 |
2 |
3 |
4 |
5 |
6 |
7 | DBD Stats
8 |
9 |
10 | News
11 |
12 |
13 | Characters
14 |
15 |
16 | Offerings
17 |
18 |
19 | Perks
20 |
21 |
22 | Shrine
23 |
24 |
25 | Player Stats
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
![]()
9 |
10 |
11 | {{item.displayName}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
60 |
61 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/components/OfferingList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
![]()
9 |
10 |
11 | {{item.displayName}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
69 |
70 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/components/PerkList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
![]()
9 |
10 |
11 | {{item.displayName}}
12 |
13 |
14 |
15 | (This perk is teachable at level {{item.teachableOnBloodWebLevel}})
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
95 |
96 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/components/TunablesTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Tunable Values
5 |
6 |
7 |
8 |
Killer Tunables
9 |
10 |
11 |
12 |
Base Killer Tunables
13 |
(These apply to every killer)
14 |
15 |
16 |
17 |
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 |
2 |
3 |
4 |
5 |
![]()
6 |
7 |
{{name}}
8 | Role: {{role}}
9 | Difficulty: {{difficulty}}
10 | Gender: {{gender}}
11 | Height: {{height}}
12 | Power: {{powerInfo.name}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Power
21 |
22 | Backstory
23 |
24 | Biography
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
159 |
160 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/views/Characters.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
Survivors
11 |
12 |
13 |
14 |
15 | {{item.displayName}}
16 |
17 |
18 |
19 |
Killers
20 |
21 |
22 |
23 |
24 | {{item.displayName}}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
71 |
72 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/views/News.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
News
11 |
12 | Live
13 | Player Test Build
14 | QA
15 | Stage
16 | Cert
17 | Developer
18 |
19 |
20 |
21 |
22 |
23 |
![]()
24 |
25 |
26 |
27 | New Content
28 | Patch Notes
29 | Dev Message
30 | New Event
31 | {{(new Date(item.startDate)).toLocaleDateString()}}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
94 |
95 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/views/Offerings.vue:
--------------------------------------------------------------------------------
1 |
2 |
33 |
34 |
35 |
103 |
104 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/views/Perks.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
18 |
25 |
33 |
34 |
35 |
36 |
104 |
105 |
--------------------------------------------------------------------------------
/DBD-API/ClientApp/src/views/Shrine.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Shrine Of Secrets
5 |
Resets in {{shrineTime}}
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
![]()
20 |
21 |
22 |
23 |
24 |
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 |
--------------------------------------------------------------------------------