├── .gitignore ├── COPYING.txt ├── README-profile.md ├── README-saves.md ├── README.md ├── bl3save ├── OakProfile_pb2.py ├── OakSave_pb2.py ├── OakShared_pb2.py ├── __init__.py ├── bl3profile.py ├── bl3save.py ├── cli_archive.py ├── cli_common.py ├── cli_copy_pt.py ├── cli_edit.py ├── cli_import_json.py ├── cli_import_protobuf.py ├── cli_info.py ├── cli_prof_edit.py ├── cli_prof_import_json.py ├── cli_prof_import_protobuf.py ├── cli_prof_info.py ├── datalib.py └── resources │ ├── .gitignore │ ├── README.md │ ├── balance_name_mapping.json.xz │ ├── balance_to_inv_key.json.xz │ ├── gen_inventory_db.py │ └── inventoryserialdb.json.xz ├── mod_testing_gear.txt ├── protobufs ├── OakProfile.proto ├── OakSave.proto ├── OakShared.proto └── README.md ├── release.txt ├── requirements-build.txt ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.sav 2 | *.pbraw 3 | *.json 4 | *.csv 5 | *.html 6 | step 7 | __pycache__ 8 | *.swp 9 | bl3_cli_saveedit.egg-info 10 | build 11 | dist 12 | -------------------------------------------------------------------------------- /COPYING.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 2 | 3 | This software is provided 'as-is', without any express or implied warranty. 4 | In no event will the authors be held liable for any damages arising from 5 | the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software in a 13 | product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | -------------------------------------------------------------------------------- /README-profile.md: -------------------------------------------------------------------------------- 1 | # Borderlands 3 Commandline Profile Editor - Profile Editing Reference 2 | 3 | This is the documentation for the profile editing portions of the 4 | BL3 CLI Savegame Editor. For general app information, installation, 5 | upgrade procedures, and other information, please see the main 6 | [README file](README.md). 7 | 8 | These docs will assume that you've installed via `pip3` - if you're using 9 | a Github checkout, substitute the commands as appropriate. The equivalent 10 | commands will be: 11 | 12 | python -m bl3save.cli_prof_edit -h 13 | python -m bl3save.cli_prof_info -h 14 | python -m bl3save.cli_prof_import_protobuf -h 15 | python -m bl3save.cli_prof_import_json -h 16 | 17 | # Table of Contents 18 | 19 | - [Basic Operation](#basic-operation) 20 | - [Output Formats](#output-formats) 21 | - [Modifying the Profile](#modifying-the-profile) 22 | - [Keys](#keys) 23 | - [Golden Keys](#golden-keys) 24 | - [Diamond Keys](#diamond-keys) 25 | - [Vault Card 1 Keys](#vault-card-1-keys) 26 | - [Vault Card 1 Chests](#vault-card-1-chests) 27 | - [Vault Card 2 Keys](#vault-card-2-keys) 28 | - [Vault Card 2 Chests](#vault-card-2-chests) 29 | - [Vault Card 3 Keys](#vault-card-3-keys) 30 | - [Vault Card 3 Chests](#vault-card-3-chests) 31 | - [Guardian Rank](#guardian-rank) 32 | - [Zeroing Guardian Rank](#zeroing-guardian-rank) 33 | - [Minimizing Guardian Rank](#minimizing-guardian-rank) 34 | - [Guardian Rank Rewards](#guardian-rank-rewards) 35 | - [Guardian Rank Tokens](#guardian-rank-tokens) 36 | - [Borderlands Science](#borderlands-science) 37 | - [Maximizing Borderlands Science Progression](#maximizing-borderlands-science-progression) 38 | - [Resetting Borderlands Science Progression](#resetting-borderlands-science-progression) 39 | - [Removing active Borderlands Science boosts](#removing-active-borderlands-science-boosts) 40 | - [Borderlands Science tokens](#borderlands-science-tokens) 41 | - [Bank Item Levels](#bank-item-levels) 42 | - [Bank Item Mayhem Levels](#bank-item-mayhem-levels) 43 | - [Alphabetize Customizations](#alphabetize-customizations) 44 | - [Clear Customizations](#clear-customizations) 45 | - [Unlocks](#unlocks) 46 | - [Lost Loot and Bank Capacity](#lost-loot-and-bank-capacity) 47 | - [Customizations](#customizations) 48 | - [All Unlocks at Once](#all-unlocks-at-once) 49 | - [Import Bank Items](#import-bank-items) 50 | - [Importing Raw Protobufs](#importing-raw-protobufs) 51 | - [Importing JSON](#importing-json) 52 | - [Profile Info Usage](#profile-info-usage) 53 | - [Items/Inventory](#itemsinventory) 54 | 55 | # Basic Operation 56 | 57 | At its most basic, you can run the editor with only an input and output 58 | file, and it will simply load and then re-encode the profile. For 59 | instance, in this example, `profile.sav` and `newprofile.sav` will be 60 | identical as far as BL3 is concerned: 61 | 62 | bl3-profile-edit profile.sav newprofile.sav 63 | 64 | If `newprofile.sav` exists, the utility will prompt you if you want to 65 | overwrite it. If you want to force the utility to overwrite without asking, 66 | use the `-f`/`--force` option: 67 | 68 | bl3-profile-edit profile.sav newprofile.sav -f 69 | 70 | As the app processes files, it will output exactly what it's doing. If 71 | you prefer to have silent output (unless there's an error), such as if 72 | you're using this to process a group of files in a loop, you can use 73 | the `-q`/`--quiet` option: 74 | 75 | bl3-profile-edit profile.sav newprofile.sav -q 76 | 77 | Note that currently, the app will refuse to overwrite the same file that 78 | you're editing. You'll need to move/rename the `newprofile.sav` over the 79 | original, if you want it to replace your current profile. Be sure to keep 80 | backups! 81 | 82 | # Output Formats 83 | 84 | The editor can output files in a few different formats, and you can 85 | specify the format using the `-o`/`--output` option, like so: 86 | 87 | bl3-profile-edit profile.sav newprofile.sav -o profile 88 | bl3-profile-edit profile.sav newprofile.pbraw -o protobuf 89 | bl3-profile-edit profile.sav newprofile.json -o json 90 | bl3-profile-edit profile.sav newprofile.txt -o items 91 | 92 | - **profile** - This is the default, if you don't specify an output 93 | format. It will save the game like a valid BL3 profile. This 94 | will likely be your most commonly-used option. 95 | - **protobuf** - This will write out the raw, unencrypted Protobuf 96 | entries contained in the profile, which might be useful if you 97 | want to look at them with a Protobuf viewer of some sort (such 98 | as [this one](https://protogen.marcgravell.com/decode)), or to 99 | make hand edits of your own. Raw protobuf files can be imported 100 | back into the profile using the separate `bl3-profile-import-protobuf` 101 | command, whose docs you can find near the bottom of this README. 102 | - **json** - Alternatively, this will write out the raw protobufs 103 | as encoded into JSON. Like the protobuf output, you should be 104 | able to edit this by hand and then re-import using the 105 | `bl3-profile-import-json` utility. **NOTE:** JSON import is not 106 | super well-tested yet, so keep backups! 107 | - **items** - This will output a text file containing item codes 108 | for all items in your bank, which can be read back in to other 109 | savegames or profiles. It uses a format similar to the item codes 110 | used by Gibbed's BL2/TPS editors. (It will probably be identical 111 | to the codes used by Gibbed's BL3 editor, once that is released, 112 | but time will tell on that front.) 113 | - You can optionally specify the `--csv` flag to output a CSV file 114 | instead of "regular" text file. The first column will be the 115 | item names, the second will be the item codes. 116 | 117 | Keep in mind that when saving in `items` format, basically all of 118 | the other CLI arguments are pointless, since the app will only save 119 | out the items textfile. 120 | 121 | # Modifying the Profile 122 | 123 | Here's a list of all the edits you can make to the profile. You 124 | can specify as many of these as you want on the commandline, to 125 | process multiple changes at once. 126 | 127 | ## Keys 128 | 129 | The Profile editor supports editing the various keys that are acquired 130 | throughout the game 131 | 132 | ### Golden Keys 133 | 134 | The number of Golden Keys stored in the profile can be set using 135 | the `--golden-keys` argument: 136 | 137 | bl3-profile-edit profile.sav newprofile.sav --golden-keys 150 138 | 139 | ### Diamond Keys 140 | 141 | The number of Diamond Keys stored in the profile can be set using 142 | the `--diamond-keys` argument: 143 | 144 | bl3-profile-edit profile.sav newprofile.sav --diamond-keys 3 145 | 146 | ### Vault Card 1 Keys 147 | 148 | The number of keys for the first Vault Card stored in the profile can 149 | be set using the `--vaultcard1-keys` argument: 150 | 151 | bl3-profile-edit profile.sav newprofile.sav --vaultcard1-keys 10 152 | 153 | ### Vault Card 1 Chests 154 | 155 | The number of chests available to open, for the first Vault Card stored 156 | in the profile, can be set using the `--vaultcard1-chests` argument: 157 | 158 | bl3-profile-edit profile.sav newprofile.sav --vaultcard1-chests 10 159 | 160 | ### Vault Card 2 Keys 161 | 162 | The number of keys for the second Vault Card stored in the profile can 163 | be set using the `--vaultcard2-keys` argument: 164 | 165 | bl3-profile-edit profile.sav newprofile.sav --vaultcard2-keys 10 166 | 167 | ### Vault Card 2 Chests 168 | 169 | The number of chests available to open, for the second Vault Card stored 170 | in the profile, can be set using the `--vaultcard2-chests` argument: 171 | 172 | bl3-profile-edit profile.sav newprofile.sav --vaultcard2-chests 10 173 | 174 | ### Vault Card 3 Keys 175 | 176 | The number of keys for the third Vault Card stored in the profile can 177 | be set using the `--vaultcard3-keys` argument: 178 | 179 | bl3-profile-edit profile.sav newprofile.sav --vaultcard3-keys 10 180 | 181 | ### Vault Card 3 Chests 182 | 183 | The number of chests available to open, for the third Vault Card stored 184 | in the profile, can be set using the `--vaultcard3-chests` argument: 185 | 186 | bl3-profile-edit profile.sav newprofile.sav --vaultcard3-chests 10 187 | 188 | ## Guardian Rank 189 | 190 | There are a number of functions available for managing Guardian Rank 191 | in profiles. Remember in all cases that if Guardian Rank in a profile 192 | is zeroed out, it will "inherit" the Guardian Rank from the first 193 | savegame that's loaded. Likewise, a zeroed-out GR in a savegame will 194 | inherit the current GR from the profile. When editing the profile 195 | Guardian Rank, it might be prudent to zero out all your savegames 196 | using the save editor's `--zero-guardian-rank` afterwards, to make 197 | sure that everything stays in sync. 198 | 199 | ### Zeroing Guardian Rank 200 | 201 | Guardian rank can be completely cleared from a profile using the 202 | `--zero-guardian-rank` argument. This might be useful if your 203 | profile's Guardian Rank got "infected" with Guardian Rank from 204 | someone else's save, from back before Gearbox fixed that particular 205 | bug, and you wanted to clear it out. Remember that the profile will 206 | probably "inherit" the GR from the first-loaded savegame, so make sure 207 | that the save has what you want first. The `--min-guardian-rank` 208 | option below might be better, if you're looking to start from scratch. 209 | 210 | Anyway, there's probably not much call for it nowadays, but here it is, 211 | just in case. 212 | 213 | bl3-profile-edit profile.sav newprofile.sav --zero-guardian-rank 214 | 215 | ### Minimizing Guardian Rank 216 | 217 | This is a "safer" version of `--zero-guardian-rank` which starts you 218 | off with 18 Guardian Rank, with one point in each of the Guardian Rank 219 | Rewards. This will prevent the profile from picking up the GR status 220 | from the first savegame that it loads. So if you're looking to reset your 221 | GR and start over from scratch, this is probably the better of the two 222 | options. The argument is called `--min-guardian-rank`: 223 | 224 | bl3-profile-edit profile.sav newprofile.sav --min-guardian-rank 225 | 226 | ### Guardian Rank Rewards 227 | 228 | To set the specified investment in Guardian Rank Rewards, across all 229 | eighteen rewards, use the `--guardian-rank-rewards` argument. The value 230 | you use should be the number of times you want each reward to be 231 | "redeemed." For example, to put everything at about 14%: 232 | 233 | bl3-profile-edit profile.sav newprofile.sav --guardian-rank-rewards 40 234 | 235 | Specifying `1` for the value is nearly identical to using `--min-guardian-rank`, 236 | except that this argument doesn't clear out the Guardian XP reported by 237 | the game, or the available token count. This argument will also update your 238 | main Guardian Rank number to be an appropriate value, given the number of 239 | rewards and available tokens. 240 | 241 | ### Guardian Rank Tokens 242 | 243 | The number of available Guardian Rank tokens can be set using the 244 | `--guardian-rank-tokens` argument: 245 | 246 | bl3-profile-edit profile.sav newprofile.sav --guardian-rank-tokens 10 247 | 248 | This argument will also update your main Guardian Rank number to be an 249 | appropriate value, given the number of rewards and available tokens. 250 | 251 | ## Borderlands Science 252 | 253 | There are a few functions available to help managing Borderlands Science. 254 | 255 | ### Maximizing Borderlands Science Progression 256 | 257 | By using `--max-borderlands-science` you can maximize progression, unlocking 258 | True Tannis and skipping the tutorial. 259 | 260 | bl3-profile-edit profile.sav newprofile.sav --max-borderlands-science 261 | 262 | ### Resetting Borderlands Science Progression 263 | 264 | You can also reset borderlands science progression using `--reset-borderlands-science`. 265 | This will put you back at no puzzles solved, with the tutorial incomplete. 266 | 267 | bl3-profile-edit profile.sav newprofile.sav --reset-borderlands-science 268 | 269 | ### Removing active Borderlands Science boosts 270 | 271 | You can remove any active Borderlands Science boosts using 272 | `--remove_borderlands_science_boosts`. 273 | 274 | bl3-profile-edit profile.sav newprofile.sav --remove_borderlands_science_boosts 275 | 276 | ### Borderlands Science tokens 277 | 278 | The number of available Borderlands Science tokens can be set using the 279 | `--borderlands-science-tokens` argument: 280 | 281 | bl3-profile-edit profile.sav newprofile.sav --borderlands-science-tokens 10000 282 | 283 | ## Bank Item Levels 284 | 285 | There are two arguments to set item levels for gear that's stored in 286 | your bank. The first is to set all items/weapons in the bank to the 287 | max level in the game. This can be done with `--item-levels-max` 288 | 289 | bl3-profile-edit profile.sav newprofile.sav --item-levels-max 290 | 291 | Alternatively, you can set an explicit level using `--item-levels` 292 | 293 | bl3-profile-edit profile.sav newprofile.sav --item-levels 57 294 | 295 | ## Bank Item Mayhem Levels 296 | 297 | There are two arguments to set bank item mayhem levels. The first is 298 | to set all weapons to the maximum mayhem level, which is currently 10, 299 | using `--item-mayhem-max`. Note that currently only weapons and 300 | grenades can have Mayhem applied; other items will end up generating 301 | a message like ` were unable to be levelled`. 302 | 303 | bl3-profile-edit profile.sav newprofile.sav --item-mayhem-max 304 | 305 | Alternatively, you can specify a specific Mayhem level with 306 | `--item-mayhem-levels`: 307 | 308 | bl3-profile-edit profile.sav newprofile.sav --item-mayhem-levels 5 309 | 310 | To remove Mayhem levels from weapons/grenades entirely, specify `0` for 311 | `--item-mayhem-levels`. 312 | 313 | ## Alphabetize Customizations 314 | 315 | Room Decorations, Weapon Trinkets, and Weapon Skins show up in the game 316 | in the order in which they were picked up, generally, which makes it 317 | sometimes hard to find the one you're looking for. The `--alpha` option 318 | will rearrange the data so that they're in alphabetical order, so you'll 319 | have a nice ordered list to choose from: 320 | 321 | bl3-profile-edit profile.sav newprofile.sav --alpha 322 | 323 | ## Clear Customizations 324 | 325 | If for some reason you'd like to clear your profile of all found 326 | customizations, you can do so with `--clear-customizations`. (This was 327 | honestly mostly just useful to myself when testing the app.) 328 | 329 | bl3-profile-edit profile.sav newprofile.sav --clear-customizations 330 | 331 | ## Unlocks 332 | 333 | There are a number of things you can unlock with the utility, all 334 | specified using the `--unlock` argument. You can specify this 335 | multiple times on the commandline, to unlock more than one thing 336 | at once, like so: 337 | 338 | bl3-profile-edit profile.sav newprofile.sav --unlock lostloot --unlock bank 339 | 340 | ### Lost Loot and Bank Capacity 341 | 342 | The `lostloot` and `bank` unlocks will give you the maximum number 343 | of SDUs for the Lost Loot machine and Bank, respectively: 344 | 345 | bl3-profile-edit profile.sav newprofile.sav --unlock lostloot 346 | bl3-profile-edit profile.sav newprofile.sav --unlock bank 347 | 348 | ### Customizations 349 | 350 | You can specify various types of cosmetics to unlock individually, 351 | which will give you all known customizations of that type. (Note that 352 | as new content is released, this editor will have to be updated to 353 | include the new customizations.) They can be individually unlocked 354 | with any of the following: 355 | 356 | bl3-profile-edit profile.sav newprofile.sav --unlock skins 357 | bl3-profile-edit profile.sav newprofile.sav --unlock heads 358 | bl3-profile-edit profile.sav newprofile.sav --unlock echothemes 359 | bl3-profile-edit profile.sav newprofile.sav --unlock emotes 360 | bl3-profile-edit profile.sav newprofile.sav --unlock decos 361 | bl3-profile-edit profile.sav newprofile.sav --unlock weaponskins 362 | bl3-profile-edit profile.sav newprofile.sav --unlock trinkets 363 | 364 | Alternatively, you can unlock *all* customizations all at once, by 365 | using the `customizations` unlock: 366 | 367 | bl3-profile-edit profile.sav newprofile.sav --unlock customizations 368 | 369 | **Note:** DLC-locked customizations, such as the Gold Pack, or any 370 | customization specific to a story DLC, will remain unavailable even 371 | if unlocked via this utility. If you later purchase the DLC in question, 372 | though, the relevant cosmetics should show up as available immediately. 373 | 374 | ### All Unlocks at Once 375 | 376 | You can also use `all` to unlock all the various `--unlock` 377 | options at once, without having to specify each one individually: 378 | 379 | bl3-profile-edit profile.sav newprofile.sav --unlock all 380 | 381 | ## Import Bank Items 382 | 383 | The `-i`/`--import-items` option will let you import items into 384 | your bank, of the sort you can export using `-o items`. Simply 385 | specify a text file as the argument to `-i` and it will load in 386 | any line starting with `BL3(` as an item into the savegame: 387 | 388 | bl3-profile-edit profile.sav newprofile.sav -i items.txt 389 | 390 | Note that by default, the app will not allow Fabricators to be 391 | imported into the bank, since the player doesn't have a good way to 392 | get rid of them. You can tell the app to allow importing 393 | Fabricators anyway with the `--allow-fabricator` option (which has 394 | no use when not used along with `-i`/`--import-items`) 395 | 396 | bl3-profile-edit profile.sav newprofile.sav -i items.txt --allow-fabricator 397 | 398 | If the utility can't tell what an item is during import (which may 399 | happen if BL3 has been updated but this editor hasn't been updated 400 | yet), it will refuse to import the unknown items, unless 401 | `--allow-fabricator` is specified, since the unknown item could be 402 | a Fabricator. Other edits and imports can still happen, however. 403 | 404 | If you have items saved in a CSV file (such as one exported using 405 | `-o items --csv`), you can add the `--csv` argument to import items 406 | from the CSV: 407 | 408 | bl3-profile-edit profile.sav newprofile.sav -i items.csv --csv 409 | 410 | When reading CSV files, any valid BL3 item code found by itself in 411 | a field in the CSV will be imported, so the CSV doesn't have to 412 | be in the exact same format as the ones generated by `-o items --csv`. 413 | 414 | # Importing Raw Protobufs 415 | 416 | If you've saved a profile in raw protobuf format (using the 417 | `-o protobuf` option, or otherwise), you may want to re-import it 418 | into the profile, perhaps after having edited it by hand. This can 419 | be done with the separate utility `bl3-profile-import-protobuf`. This 420 | requires a `-p`/`--protobuf` argument to specify the file where 421 | the raw protobuf is stored, and a `-t`/`--to-filename` argument, 422 | which specifies the filename to import the protobufs into: 423 | 424 | bl3-profile-import-protobuf -p edited.pbraw -t profile.sav 425 | 426 | By default this will prompt for confirmation before actually 427 | overwriting the file, but you can use the `-c`/`--clobber` option 428 | to force it to overwrite without asking: 429 | 430 | bl3-profile-import-protobuf -p edited.pbraw -t profile.sav -c 431 | 432 | **NOTE:** This (and the JSON import) is the one place where these 433 | utilities *expect* to overwrite the file you're giving it. In the 434 | above examples, it requires an existing `old.sav` file, and the 435 | savefile contents will be written directly into that file. This 436 | option does *not* currently create a brand-new valid savegame for 437 | you. 438 | 439 | # Importing JSON 440 | 441 | If you saved a profile in JSON format (using the `-o json` option), 442 | you may want to re-import it into the profile, perhaps after having 443 | edited it by hand. This can be done with the separate utility 444 | `bl3-profile-import-json`. This requires a `-j`/`--json` argument to 445 | specify the file where the JSON is stored, and a `-t`/`--to-filename` 446 | argument, which specifies the filename to import the JSON into: 447 | 448 | bl3-profile-import-json -j edited.json -t profile.sav 449 | 450 | By default this will prompt for confirmation before actually 451 | overwriting the file, but you can use the `-c`/`--clobber` option 452 | to force it to overwrite without asking: 453 | 454 | bl3-profile-import-json -j edited.json -t profile.sav -c 455 | 456 | **NOTE:** This (and the protobuf import) is the one place where these 457 | utilities *expect* to overwrite the file you're giving it. In the 458 | above examples, it requires an existing `old.sav` file, and the 459 | savefile contents will be written directly into that file. This 460 | option does *not* currently create a brand-new valid savegame for 461 | you. 462 | 463 | # Profile Info Usage 464 | 465 | The `bl3-profile-info` script is extremely simple, and just dumps a bunch 466 | of information about the specified savegame to the console. If you 467 | specify the `-v`/`--verbose` option, it'll output a bunch more info 468 | than it ordinarily would, such as bank and lost loot contents: 469 | 470 | bl3-save-info -v profile.sav 471 | 472 | Instead of doing a global "verbose" option, you can instead choose 473 | to output just some of the extra information, though at the moment there's 474 | only one extra option, so the two are identical: 475 | 476 | ## Items/Inventory 477 | 478 | The `-i`/`--items` argument will output your bank and Lost Loot machine 479 | contents, including item codes which could be put in a text file for 480 | later import: 481 | 482 | bl3-profile-info -i profile.sav 483 | -------------------------------------------------------------------------------- /README-saves.md: -------------------------------------------------------------------------------- 1 | # Borderlands 3 Commandline Savegame Editor - Savegame Editing Reference 2 | 3 | This is the documentation for the save editing portions of the 4 | BL3 CLI Savegame Editor. For general app information, installation, 5 | upgrade procedures, and other information, please see the main 6 | [README file](README.md). 7 | 8 | These docs will assume that you've installed via `pip3` - if you're using 9 | a Github checkout, substitute the commands as appropriate. The equivalent 10 | commands will be: 11 | 12 | python -m bl3save.cli_edit -h 13 | python -m bl3save.cli_info -h 14 | python -m bl3save.cli_import_protobuf -h 15 | python -m bl3save.cli_import_json -h 16 | python -m bl3save.cli_archive -h 17 | 18 | # Table of Contents 19 | 20 | - [Basic Operation](#basic-operation) 21 | - [Output Formats](#output-formats) 22 | - [Modifying the Savegame](#modifying-the-savegame) 23 | - [Character Name](#character-name) 24 | - [Save Game ID](#save-game-id) 25 | - [Save Game GUID](#save-game-guid) 26 | - [Guardian Rank](#guardian-rank) 27 | - [Character Level](#character-level) 28 | - [Mayhem Level](#mayhem-level) 29 | - [Mayhem Random Seed](#mayhem-random-seed) 30 | - [Currency (Money and Eridium)](#currency-money-and-eridium) 31 | - [Takedown Discovery Missions](#takedown-discovery-missions) 32 | - [Mission Deletion](#mission-deletion) 33 | - [Seasonal Event Status](#seasonal-event-status) 34 | - [Item Levels](#item-levels) 35 | - [Item Mayhem Levels](#item-mayhem-levels) 36 | - [Wipe Inventory](#wipe-inventory) 37 | - [Unlocks](#unlocks) 38 | - [Ammo/Backpack Unlocks](#ammobackpack-unlocks) 39 | - [Eridian Resonator](#eridian-resonator) 40 | - [Eridian Analyzer](#eridian-analyzer) 41 | - [Inventory Slots](#inventory-slots) 42 | - [Vehicles](#vehicles) 43 | - [Vehicle Skins](#vehicle-skins) 44 | - [TVHM](#tvhm) 45 | - [Eridian Cube Puzzle](#eridian-cube-puzzle) 46 | - [All Unlocks at Once](#all-unlocks-at-once) 47 | - [Copy Playthrough States](#copy-playthrough-states) 48 | - [NVHM to TVHM](#nvhm-to-tvhm) 49 | - [TVHM to NVHM](#tvhm-to-nvhm) 50 | - ["Un-Finish" NVHM](#un-finish-nvhm) 51 | - [Import Items](#import-items) 52 | - [Importing Raw Protobufs](#importing-raw-protobufs) 53 | - [Importing JSON](#importing-json) 54 | - [Savegame Info Usage](#savegame-info-usage) 55 | - [Items/Inventory](#itemsinventory) 56 | - [Fast Travel Stations](#fast-travel-stations) 57 | - [Challenges](#challenges) 58 | - [Missions](#missions) 59 | 60 | # Basic Operation 61 | 62 | At its most basic, you can run the editor with only an input and output 63 | file, and it will simply load and then re-encode the savegame. For 64 | instance, in this example, `old.sav` and `new.sav` will be identical as 65 | far as BL3 is concerned: 66 | 67 | bl3-save-edit old.sav new.sav 68 | 69 | If `new.sav` exists, the utility will prompt you if you want to overwrite 70 | it. If you want to force the utility to overwrite without asking, 71 | use the `-f`/`--force` option: 72 | 73 | bl3-save-edit old.sav new.sav -f 74 | 75 | As the app processes files, it will output exactly what it's doing. If 76 | you prefer to have silent output (unless there's an error), such as if 77 | you're using this to process a group of files in a loop, you can use 78 | the `-q`/`--quiet` option: 79 | 80 | bl3-save-edit old.sav new.sav -q 81 | 82 | Note that currently, the app will refuse to overwrite the same file that 83 | you're editing. You'll need to move/rename the `new.sav` over the 84 | original, if you want it to replace your current save. Be sure to keep 85 | backups! 86 | 87 | # Output Formats 88 | 89 | The editor can output files in a few different formats, and you can 90 | specify the format using the `-o`/`--output` option, like so: 91 | 92 | bl3-save-edit old.sav new.sav -o savegame 93 | bl3-save-edit old.sav new.pbraw -o protobuf 94 | bl3-save-edit old.sav new.json -o json 95 | bl3-save-edit old.sav new.txt -o items 96 | 97 | - **savegame** - This is the default, if you don't specify an output 98 | format. It will save the game like a valid BL3 savegame. This 99 | will likely be your most commonly-used option. 100 | - **protobuf** - This will write out the raw, unencrypted Protobuf 101 | entries contained in the savegame, which might be useful if you 102 | want to look at them with a Protobuf viewer of some sort (such 103 | as [this one](https://protogen.marcgravell.com/decode)), or to 104 | make hand edits of your own. Raw protobuf files can be imported 105 | back into savegames using the separate `bl3-save-import-protobuf` 106 | command, whose docs you can find near the bottom of this README. 107 | - **json** - Alternatively, this will write out the raw protobufs 108 | as encoded into JSON. Like the protobuf output, you should be 109 | able to edit this by hand and then re-import using the 110 | `bl3-save-import-json` utility. **NOTE:** JSON import is not 111 | super well-tested yet, so keep backups! 112 | - **items** - This will output a text file containing item codes 113 | which can be read back in to other savegames. It uses a format 114 | similar to the item codes used by Gibbed's BL2/TPS editors. 115 | (It will probably be identical to the codes used by Gibbed's BL3 116 | editor, once that is released, but time will tell on that front.) 117 | - You can optionally specify the `--csv` flag to output a CSV file 118 | instead of "regular" text file. The first column will be the 119 | item names, the second will be the item codes. 120 | 121 | Keep in mind that when saving in `items` format, basically all of 122 | the other CLI arguments are pointless, since the app will only save 123 | out the items textfile. 124 | 125 | # Modifying the Savegame 126 | 127 | Here's a list of all the edits you can make to the savegame. You 128 | can specify as many of these as you want on the commandline, to 129 | process multiple changes at once. 130 | 131 | ## Character Name 132 | 133 | This can be done with the `--name` option: 134 | 135 | bl3-save-edit old.sav new.sav --name "Gregor Samsa" 136 | 137 | ## Save Game ID 138 | 139 | Like with BL2/TPS, I suspect that this ID isn't at all important, but 140 | the editor can set it anyway with the `--save-game-id` option. BL3 141 | itself sets the savegame ID to match the filename of the savegame, if 142 | interpreted as a hex value (so `10.sav` would have an ID of `16`). 143 | 144 | bl3-save-edit old.sav new.sav --save-game-id 2 145 | 146 | ## Save Game GUID 147 | 148 | This is another identifier I suspect isn't actually important at all, 149 | but there's an internal GUID which can be used to identify savegames 150 | uniquely. The `--randomize-guid` option will randomize this value, 151 | so if you're copying a character or something you can ensure that the 152 | value is different between them. As I say, I have no idea if that's 153 | ever actually important (I've copied characters many times without 154 | randomizing this value, and they've worked fine) but here's the 155 | option anyway: 156 | 157 | bl3-save-edit old.sav new.sav --randomize-guid 158 | 159 | ## Guardian Rank 160 | 161 | Guardian Rank can be zeroed out using the `--zero-guardian-rank` 162 | argument. This is useful when a savegame gets out of sync from your 163 | profile, such as if you download a savegame from the internet, or if 164 | you're making changes to values in your profile. After zeroing out 165 | the Guardian Rank in a savegame, the next time the character is loaded 166 | into the game, it will inherit the main profile's Guardian Rank. 167 | 168 | bl3-save-edit old.sav new.sav --zero-guardian-rank 169 | 170 | ## Character Level 171 | 172 | You can set your character to a specific level using `--level `, 173 | or to the max level allowed by the game using `--level-max` 174 | 175 | bl3-save-edit old.sav new.sav --level 20 176 | bl3-save-edit old.sav new.sav --level-max 177 | 178 | ## Mayhem Level 179 | 180 | This is only really useful before you've got Mayhem Mode unlocked. 181 | You can use the `--mayhem` argument to activate Mayhem mode even from 182 | the very beginning of the game. Note that you still won't have access 183 | to the Mayhem console on Sanctuary until it's properly unlocked by the 184 | game, so this will be the only way of changing Mayhem mode until that 185 | point in the game. This will set the Mayhem level for all 186 | playthroughs found in the game. 187 | 188 | bl3-save-edit old.sav new.sav --mayhem 10 189 | 190 | Note that in order to have Anointments drop while playing in Normal 191 | mode, your savegame does need to have THVM unlocked, so see the `--unlock` 192 | docs below for how to do that. 193 | 194 | ## Mayhem Random Seed 195 | 196 | The active modifiers in Mayhem Mode are all generated using a single 197 | [random seed](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) 198 | which is specified on a per-playthrough basis. If you have a set of 199 | Mayhem modifiers you like, you can take that seed (shown in the `bl3-save-info` 200 | output) and set it onto another save using the `--mayhem-seed` argument: 201 | 202 | bl3-save-edit old.sav new.save --mayhem-seed -3938591 203 | 204 | The seeds may be positive or negative, and can range up to 2 billion or 205 | so, both negative and positive. Note that if Gearbox changes the pool of 206 | available Mayhem modifiers, such as by disabling some, or adding new ones, 207 | the random seeds you're used to will start to give a different result, so 208 | you may have to reroll a bunch to find your favorite set, again. 209 | 210 | ## Currency (Money and Eridium) 211 | 212 | Money and Eridium can be set with the `--money` and `--eridium` 213 | arguments, respectively: 214 | 215 | bl3-save-edit old.sav new.sav --money 20000000 216 | bl3-save-edit old.sav new.sav --eridium 10000 217 | 218 | ## Takedown Discovery Missions 219 | 220 | You can clear out the two Takedown Discovery missions using the 221 | `--clear-takedowns` argument, if you have a character who you don't 222 | ever plan to do Takedowns with, but want those mission-pickup notifications 223 | gone, without going through the hassle of travelling to the Takedown 224 | maps yourself: 225 | 226 | bl3-save-edit old.sav new.sav --clear-takedowns 227 | 228 | Note that if you decide to do the Takedowns later, you'll have to pilot 229 | Sanctuary over to their pickup points before they'll appear in your 230 | Fast Travel menu; this argument doesn't unlock the Fast Travel locations 231 | itself. 232 | 233 | Obviously it doesn't take long to just do the Discovery missions yourself; 234 | you can always just fail out of the Takedown itself by fast-travelling out 235 | of the map, but this option would save you a minute or two, anyway. 236 | 237 | ## Mission Deletion 238 | 239 | If you ever have a sidemission lock up, or just want to repeat a side 240 | mission, the `--delete-pt1-mission` and `--delete-pt2-mission` arguments 241 | can be used to delete the mission from your savegame so it can be picked 242 | up again. Note that this will *only* work for side missions. Plot 243 | missions aren't picked up at will like side missions are, and deleting 244 | plot missions will basically lock your character out of the rest of the 245 | game. 246 | 247 | The `pt1` argument corresponds to the first playthrough (Normal/NVHM), and 248 | the `pt2` argument corresponds to the second playthrough (TVHM). To use 249 | this argument, you'll need to know the full object path to the mission you 250 | want to delete. This can be found using `bl3-save-info --mission-paths`. 251 | Docs for that function [can be found here](#missions). 252 | 253 | bl3-save-edit old.sav new.sav --delete-pt1-mission /Game/Missions/Side/Zone_0/Prologue/Mission_UnderwearTink.Mission_UnderwearTink_C 254 | bl3-save-edit old.sav new.sav --delete-pt2-mission /Game/Missions/Side/Zone_0/Prologue/Mission_UnderwearTink.Mission_UnderwearTink_C 255 | 256 | The `--delete-pt1-mission` and `--delete-pt2-mission` arguments can be 257 | specified more than once if you'd like to delete more than one mission at 258 | a time. 259 | 260 | ## Seasonal Event Status 261 | 262 | There are a few options available to clear out seasonal event challenge 263 | status (Bloody Harvest, Broken Hearts, and Revenge of the Cartels). 264 | This will allow you to go through the challenge ranks as if you'd not 265 | played the event yet. This is admittedly a bit silly, but it might be 266 | useful when paired with the Event Rewards mods, which let you use 267 | challenge rewards from the previous years. 268 | 269 | To clear out the event challenge statuses individually: 270 | 271 | bl3-save-edit old.sav new.sav --clear-bloody-harvest 272 | bl3-save-edit old.sav new.sav --clear-broken-hearts 273 | bl3-save-edit old.sav new.sav --clear-cartels 274 | 275 | Or, you can do all three at once: 276 | 277 | bl3-save-edit old.sav new.sav --clear-all-events 278 | 279 | ## Item Levels 280 | 281 | There are two arguments to set item levels. The first is to set 282 | all items/weapons in your inventory to match your character's level. 283 | If you're also changing your character's level at the same time, 284 | items/weapons will get that new level. This can be done with 285 | `--items-to-char` 286 | 287 | bl3-save-edit old.sav new.sav --items-to-char 288 | 289 | Alternatively, you can set an explicit level using `--item-levels` 290 | 291 | bl3-save-edit old.sav new.sav --item-levels 57 292 | 293 | ## Item Mayhem Levels 294 | 295 | There are two arguments to set item mayhem levels. The first is 296 | to set all weapons to the maximum mayhem level, which is currently 10, 297 | using `--item-mayhem-max`. Note that currently only weapons and 298 | grenades can have Mayhem applied; other items will end up generating 299 | a message like ` were unable to be levelled`. 300 | 301 | bl3-save-edit old.sav new.sav --item-mayhem-max 302 | 303 | Alternatively, you can specify a specific Mayhem level with 304 | `--item-mayhem-levels`: 305 | 306 | bl3-save-edit old.sav new.sav --item-mayhem-levels 5 307 | 308 | To remove Mayhem levels from weapons/greandes entirely, specify `0` for 309 | `--item-mayhem-levels`. 310 | 311 | ## Wipe Inventory 312 | 313 | Inventory can be wiped entirely using the `--wipe-inventory` argument: 314 | 315 | bl3-save-edit old.sav new.sav --wipe-inventory 316 | 317 | This will happen prior to any item imports (see below), so you can wipe 318 | the inventory and import in the same command (and then use the above 319 | levelling commands to alter the gear after import). 320 | 321 | ## Unlocks 322 | 323 | There are a number of things you can unlock with the utility, all 324 | specified using the `--unlock` argument. You can specify this 325 | multiple times on the commandline, to unlock more than one thing 326 | at once, like so: 327 | 328 | bl3-save-edit old.sav new.sav --unlock ammo --unlock backpack 329 | 330 | ### Ammo/Backpack Unlocks 331 | 332 | The `ammo` and `backpack` unlocks will give you the maximum number 333 | of SDUs for all ammo types, and your backpack space, respectively. 334 | The `ammo` SDU unlock will also fill your ammo reserves. 335 | 336 | bl3-save-edit old.sav new.sav --unlock ammo 337 | bl3-save-edit old.sav new.sav --unlock backpack 338 | 339 | ### Eridian Resonator 340 | 341 | The `resonator` unlock is what allows you to crack open Eridium 342 | deposits throughout the game. You ordinarily receive this as a 343 | reward for the plot mission "Beneath the Meridian." 344 | 345 | bl3-save-edit old.sav new.sav --unlock resonator 346 | 347 | ### Eridian Analyzer 348 | 349 | Likewise, the `analyzer` unlock is what allows you to decode 350 | the Eridian writings scattered throughout BL3. You ordinarily 351 | receive this ability during the plot mission "The Great Vault." 352 | 353 | bl3-save-edit old.sav new.sav --unlock analyzer 354 | 355 | ### Inventory Slots 356 | 357 | You can use the `gunslots`, `artifactslot`, and `comslot` unlocks 358 | to activate the inventory slots which are ordinarily locked until 359 | certain points in the game. You can also use the `allslots` to 360 | unlock all of them at once, rather than having to specify three 361 | unlocks. 362 | 363 | bl3-save-edit old.sav new.sav --unlock gunslots 364 | bl3-save-edit old.sav new.sav --unlock comslot 365 | bl3-save-edit old.sav new.sav --unlock artifactslot 366 | 367 | Or: 368 | 369 | bl3-save-edit old.sav new.sav --unlock allslots 370 | 371 | ### Vehicles 372 | 373 | You can use the `vehicles` unlock to unlock all vehicles and 374 | vehicle parts. Note that this does *not* prematurely unlock the 375 | Catch-A-Ride system. You will still have to at least complete 376 | the story mission with Ellie which unlocks those, to have access 377 | to the vehicles. 378 | 379 | bl3-save-edit old.sav new.sav --unlock vehicles 380 | 381 | ### Vehicle Skins 382 | 383 | You can use `vehicleskins` to unlock all vehicle skins, for all 384 | vehicle types. 385 | 386 | bl3-save-edit old.sav new.sav --unlock vehicleskins 387 | 388 | ### TVHM 389 | 390 | You can use the `tvhm` unlock to unlock TVHM mode early: 391 | 392 | bl3-save-edit old.sav new.sav --unlock tvhm 393 | 394 | ### Eridian Cube Puzzle 395 | 396 | The Eridian Cube puzzle in Desolation's Edge (on Nekrotafeyo) is 397 | ordinarily only doable once per savegame. The `cubepuzzle` 398 | unlock will let you complete it more than once: 399 | 400 | bl3-save-edit old.sav new.sav --unlock cubepuzzle 401 | 402 | ### All Unlocks at Once 403 | 404 | You can also use `all` to unlock all the various `--unlock` 405 | options at once, without having to specify each one individually: 406 | 407 | bl3-save-edit old.sav new.sav --unlock all 408 | 409 | ## Copy Playthrough States 410 | 411 | ### NVHM to TVHM 412 | 413 | The `--copy-nvhm` argument can be used to copy mission status, 414 | unlocked Fast Travels, Mayhem Mode, and Last Map Visited from Normal 415 | mode (NVHM) to TVHM, so your character in TVHM will be at basically 416 | the exact same game state as in Normal. 417 | 418 | bl3-save-edit old.sav new.sav --copy-nvhm 419 | 420 | ### TVHM to NVHM 421 | 422 | The `--copy-tvhm` argument can be used to copy mission status, 423 | unlocked Fast Travels, Mayhem Mode, and Last Map Visited from TVHM 424 | to Normal mode (NVHM), so your character in Normal will be at basically 425 | the exact same game state as in TVHM. 426 | 427 | bl3-save-edit old.sav new.sav --copy-tvhm 428 | 429 | ## "Un-Finish" NVHM 430 | 431 | Alternatively, you can use the `--unfinish-nvhm` argument to 432 | completely discard all TVHM data, and set the game state so that 433 | NVHM was never finished. Note that this does *not* reset any 434 | mission status, so if you've already legitimately finished NVHM 435 | in the savegame, you won't be able to re-unlock it in-game (though 436 | `--unlock tvhm` or `--copy-nvhm` could still be used to unlock). 437 | This is primarily useful just if you wanted to undo a `--copy-nvhm` 438 | run, or for myself when testing things out using saves from my 439 | [BL3 Savegame Archive](http://apocalyptech.com/games/bl-saves/bl3.php). 440 | 441 | bl3-save-edit old.sav new.sav --unfinish-nvhm 442 | 443 | ## Import Items 444 | 445 | The `-i`/`--import-items` option will let you import items into 446 | a savegame, of the sort you can export using `-o items`. Simply 447 | specify a text file as the argument to `-i` and it will load in 448 | any line starting with `BL3(` as an item into the savegame: 449 | 450 | bl3-save-edit old.sav new.sav -i items.txt 451 | 452 | Note that by default, the app will not allow Fabricators to be 453 | imported into a save, since the player doesn't have a good way to 454 | get rid of them. You can tell the app to allow importing 455 | Fabricators anyway with the `--allow-fabricator` option (which has 456 | no use when not used along with `-i`/`--import-items`) 457 | 458 | bl3-save-edit old.sav new.sav -i items.txt --allow-fabricator 459 | 460 | If the utility can't tell what an item is during import (which may 461 | happen if BL3 has been updated but this editor hasn't been updated 462 | yet), it will refuse to import the unknown items, unless 463 | `--allow-fabricator` is specified, since the unknown item could be 464 | a Fabricator. Other edits and imports can still happen, however. 465 | 466 | If you have items saved in a CSV file (such as one exported using 467 | `-o items --csv`), you can add the `--csv` argument to import items 468 | from the CSV: 469 | 470 | bl3-save-edit old.sav new.sav -i items.csv --csv 471 | 472 | When reading CSV files, any valid BL3 item code found by itself in 473 | a field in the CSV will be imported, so the CSV doesn't have to 474 | be in the exact same format as the ones generated by `-o items --csv`. 475 | 476 | # Importing Raw Protobufs 477 | 478 | If you've saved a savegame in raw protobuf format (using the 479 | `-o protobuf` option, or otherwise), you may want to re-import it 480 | into a savegame, perhaps after having edited it by hand. This can 481 | be done with the separate utility `bl3-save-import-protobuf`. This 482 | requires a `-p`/`--protobuf` argument to specify the file where 483 | the raw protobuf is stored, and a `-t`/`--to-filename` argument, 484 | which specifies the filename to import the protobufs into: 485 | 486 | bl3-save-import-protobuf -p edited.pbraw -t old.sav 487 | 488 | By default this will prompt for confirmation before actually 489 | overwriting the file, but you can use the `-c`/`--clobber` option 490 | to force it to overwrite without asking: 491 | 492 | bl3-save-import-protobuf -p edited.pbraw -t old.sav -c 493 | 494 | **NOTE:** This (and the JSON import) is the one place where these 495 | utilities *expect* to overwrite the file you're giving it. In the 496 | above examples, it requires an existing `old.sav` file, and the 497 | savefile contents will be written directly into that file. This 498 | option does *not* currently create a brand-new valid savegame for 499 | you. 500 | 501 | # Importing JSON 502 | 503 | If you saved a savegame in JSON format (using the `-o json` option), 504 | you may want to re-import it into a savegame, perhaps after having 505 | edited it by hand. This can be done with the separate utility 506 | `bl3-save-import-json`. This requires a `-j`/`--json` argument to 507 | specify the file where the JSON is stored, and a `-t`/`--to-filename` 508 | argument, which specifies the filename to import the JSON into: 509 | 510 | bl3-save-import-json -j edited.json -t old.sav 511 | 512 | By default this will prompt for confirmation before actually 513 | overwriting the file, but you can use the `-c`/`--clobber` option 514 | to force it to overwrite without asking: 515 | 516 | bl3-save-import-json -j edited.json -t old.sav -c 517 | 518 | **NOTE:** This (and the protobuf import) is the one place where these 519 | utilities *expect* to overwrite the file you're giving it. In the 520 | above examples, it requires an existing `old.sav` file, and the 521 | savefile contents will be written directly into that file. This 522 | option does *not* currently create a brand-new valid savegame for 523 | you. 524 | 525 | # Savegame Info Usage 526 | 527 | The `bl3-save-info` script is extremely simple, and just dumps a bunch 528 | of information about the specified savegame to the console. If you 529 | specify the `-v`/`--verbose` option, it'll output a bunch more info 530 | than it ordinarily would, such as inventory contents and discovered 531 | Fast Travel stations: 532 | 533 | bl3-save-info -v old.sav 534 | 535 | Instead of doing a global "verbose" option, you can instead choose 536 | to output just some of the extra information: 537 | 538 | ## Items/Inventory 539 | 540 | The `-i`/`--items` argument will output your inventory, including item 541 | codes which could be put in a text file for later import: 542 | 543 | bl3-save-info -i old.sav 544 | 545 | ## Fast Travel Stations 546 | 547 | The `--fast-travel` argument will output a list of all unlocked 548 | Fast Travel stations, per playthrough. These are reported as the raw 549 | object name in the game, so you may have to use a 550 | [level name reference](https://github.com/BLCM/BLCMods/wiki/Level-Name-Reference#borderlands-3) 551 | to get a feel for what is what. 552 | 553 | bl3-save-info --fast-travel old.sav 554 | 555 | ## Challenges 556 | 557 | The `--all-challenges` argument will output the state of all challenges 558 | in your savegame. Note that BL3 uses challenges to keep track of a 559 | lot of info in the savegames, and this list will be over 1.5k items 560 | long! As with the fast travel stations, these will be reported as the 561 | raw object names. 562 | 563 | bl3-save-info --all-challenges old.sav 564 | 565 | ## Missions 566 | 567 | The `--all-missions` argument will output all of the missions that the 568 | character has completed, in addition to the active missions which are 569 | always shown. 570 | 571 | bl3-save-info --all-missions old.sav 572 | 573 | If you want to see the actual object paths to any reported missions 574 | (for instance, when looking to clear out sidemission progress to deal 575 | with locked missions), use the `--mission-paths` option: 576 | 577 | bl3-save-info --mission-paths old.sav 578 | bl3-save-info --all-missions --mission-paths old.sav 579 | 580 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Borderlands 3 Commandline Savegame/Profile Editor 2 | 3 | This project is a commandline Python-based Borderlands 3 Savegame 4 | and Profile Editor. It's a companion to the very similar 5 | [CLI editor for BL2/TPS](https://github.com/apocalyptech/borderlands2), 6 | and provides some very similar functionality. It can be used 7 | to level up your characters, unlock Mayhem modes early in the 8 | game, unlock TVHM, add SDUs, unlock equipment slots, and more. 9 | 10 | This editor has only been tested on PC Savegames -- other platforms' 11 | savegames are not supported at the moment. 12 | 13 | Please keep the following in mind: 14 | 15 | - This app does not have any graphical interface. You must be 16 | on a commandline in order to use it. (A rudimentary web 17 | interface is available, though -- see below.) 18 | - The app has only very limited item-editing capability at the 19 | moment, which is restricted to: 20 | - Item Levels can be changed 21 | - Mayhem Level can be set on items 22 | - It does not offer any direct ability to alter Guardian Rank status. 23 | - While I have not experienced any data loss with the app, 24 | **take backups of your savegames before using this**, and 25 | keep in mind that it could end up corrupting your saves. If 26 | you do encounter any data loss problems, please contact me 27 | and I'll try to at least fix whatever bug caused it. 28 | 29 | # Table of Contents 30 | 31 | - [Web UI](#web-ui) 32 | - [Installation](#installation) 33 | - [Upgrading](#upgrading) 34 | - [Notes for People Using Windows](#notes-for-people-using-windows) 35 | - [Running from Github](#running-from-github) 36 | - [Finding Savegames](#finding-savegames) 37 | - [Editor Usage](#editor-usage) 38 | - [TODO](#todo) 39 | - [Credits](#credits) 40 | - [License](#license) 41 | - [Other Utilities](#other-utilities) 42 | - [Changelog](#changelog) 43 | 44 | # Web UI 45 | 46 | Abram Hindle is providing a simple web-based version of this utility, 47 | so feel free to give that a try: 48 | 49 | https://abramhindle.github.io/bl3-cli-saveedit/ 50 | 51 | Yes, this will install python and bl3-cli-saveedit in your browser and 52 | give you a tiny UI to dupe your savefiles or import items. 53 | 54 | # Installation 55 | 56 | This editor requires [Python 3](https://www.python.org/), and has been 57 | tested on 3.9 through 3.12. It also requires the [protobuf package](https://pypi.org/project/protobuf/). 58 | 59 | The easiest way to install this app is via `pip`/`pip3`. Once Python 3 is 60 | installed, you should be able to run this to install the app: 61 | 62 | pip3 install bl3-cli-saveedit 63 | 64 | Once installed, there should be a few new commandline utilities available 65 | to you. The main editor is `bl3-save-edit`, and you can see its possible 66 | arguments with `-h`/`--help`: 67 | 68 | bl3-save-edit -h 69 | 70 | There's also a `bl3-save-info` utility which just shows some information 71 | about a specified savefile. You can see its possible arguments with 72 | `-h`/`--help` as well: 73 | 74 | bl3-save-info -h 75 | 76 | If you've got a raw savegame protobuf file that you've hand-edited (or 77 | otherwise processed) that you'd like to import into an existing savegame, 78 | you can do that with `bl3-save-import-protobuf`: 79 | 80 | bl3-save-import-protobuf -h 81 | 82 | Alternatively, if you've got a savegame exported as JSON that you'd like 83 | to import into an existing savegame, you can do that with 84 | `bl3-save-import-json`: 85 | 86 | bl3-save-import-json -h 87 | 88 | Finally, there's a utility which I'd used to generate my 89 | [BL3 Savegame Archive Page](http://apocalyptech.com/games/bl-saves/bl3.php). 90 | This one won't be useful to anyone but me, but you can view its arguments 91 | as well, if you like: 92 | 93 | bl3-process-archive-saves -h 94 | 95 | There are also profile-specific versions of most of those commands, which 96 | can be used to edit the main BL3 `profile.sav`: 97 | 98 | bl3-profile-edit -h 99 | bl3-profile-info -h 100 | bl3-profile-import-protobuf -h 101 | bl3-profile-import-json -h 102 | 103 | ### Upgrading 104 | 105 | When a new version is available, you can update using `pip3` like so: 106 | 107 | pip3 install --upgrade bl3-cli-saveedit 108 | 109 | You can check your current version by running any of the apps with the 110 | `-V`/`--version` argument: 111 | 112 | bl3-save-info --version 113 | 114 | ### Notes for People Using Windows 115 | 116 | This is a command-line utility, which means there's no graphical interface, 117 | and you'll have to run it from either a Windows `cmd.exe` prompt, or presumably 118 | running through PowerShell should work, too. The first step is to 119 | install Python: 120 | 121 | - The recommended way is to [install Python from python.org](https://www.python.org/downloads/windows/). 122 | Grab what's available in the 3.x series (at time of writing, that's 3.9.4), 123 | and when it's installing, make sure to check the checkbox which says something 124 | like "add to PATH", so that you can run Python from the commandline directly. 125 | - If you're on Windows 10, you can apparently just type `python3` into a command 126 | prompt to be taken to the Windows store, where you can install Python with 127 | just one click. I've heard reports that this method does *not* provide the 128 | ability to add Python to your system PATH, though, so it's possible that 129 | running it would be more complicated. 130 | 131 | When it's installed, test that you can run it from the commandline. Open up 132 | either `cmd.exe` or PowerShell, and make sure that you see something like this 133 | when you run `python -V`: 134 | 135 | C:\> python -V 136 | Python 3.9.4 137 | 138 | If that works, you can then run the `pip3 install bl3-cli-saveedit` command 139 | as mentioned above, and use the commandline scripts to edit to your heart's 140 | content. 141 | 142 | ### Running from Github 143 | 144 | Alternatively, if you want to download or run the Github version of 145 | the app: clone the repository and then install `protobuf` (you can 146 | use `pip3 install -r requirements.txt` to do so, though a `pip3 install protobuf` 147 | will also work just fine). 148 | 149 | You can then run the scripts directly from the Github checkout, though 150 | you'll have to use a slightly different syntax. For instance, rather than 151 | running `bl3-save-edit -h` to get help for the main savegame editor, you 152 | would run: 153 | 154 | python -m bl3save.cli_edit -h 155 | 156 | The equivalents for each of the commands are listed in their individual 157 | README files, linked below. 158 | 159 | ### Finding Savegames 160 | 161 | This app doesn't actually know *where* your savegames or profiles are located. 162 | When you give it a filename, it'll expect that the file lives in your "current" 163 | directory, unless the filename includes all its path information. When launching 164 | a `cmd.exe` on Windows, for instance, you'll probably start out in your home 165 | directory (`C:\Users\username`), but your savegames will actually live in a 166 | directory more like `C:\Users\username\My Documents\My Games\Borderlands 3\Saved\SaveGames\\`. 167 | The easiest way to run the utilities is to just use `cd` to go into the dir 168 | where your saves are (or otherwise launch your commandline in the directory you 169 | want). Otherwise, you could copy the save into your main user dir (and then 170 | copy back after editing), or even specify the full paths with the filenames. 171 | 172 | # Editor Usage 173 | 174 | For instructions on using the Savegame portions of the editor, see 175 | [README-saves.md](https://github.com/apocalyptech/bl3-cli-saveedit/blob/master/README-saves.md). 176 | 177 | FOr instructions on using the Profile portions of the editor, see 178 | [README-profile.md](https://github.com/apocalyptech/bl3-cli-saveedit/blob/master/README-profile.md). 179 | 180 | # TODO 181 | 182 | - Would anyone appreciate an option to *delete* Fabricators? Hm. 183 | - Would be nice to have some anointment-setting functions in here. 184 | - Did we want a function to *clear* Vault Card progress? 185 | - PS4 Support (for already-unlocked saves, anyway) 186 | - If we fail to read a savefile or profile, might be nice to *actually* check 187 | if it's the other of profile-or-savefile, and give a more helpful message in 188 | those cases. 189 | - Something a bit more Enum-like for various things in `__init__.py`; I 190 | know that's not very Pythonic, but when dealing with extra-Python data 191 | formats, one must sometimes make exceptions. 192 | - Unit tests? 193 | - Convert `setup.py` to `pyproject.toml` 194 | - Check for minimum Python version on startup, rather than allowing older 195 | versions to fail with a probably-confusing error 196 | 197 | # Credits 198 | 199 | The encryption/decryption stanzas in `BL3Save.__init__` and `BL3Save.save_to` 200 | were [helpfully provided by Gibbed](https://twitter.com/gibbed/status/1246863435868049410?s=19) 201 | (rick 'at' gibbed 'dot' us), so many thanks for that! The protobuf definitions 202 | are also provided by Gibbed, from his 203 | [Borderlands3Protos](https://github.com/gibbed/Borderlands3Protos) repo, 204 | and used with permission. Gibbed also kindly provided the exact hashing 205 | mechanism used to work with weapon skins and trinkets. 206 | 207 | The rest of the savegame format was gleaned from 13xforever/Ilya's 208 | `gvas-converter` project: https://github.com/13xforever/gvas-converter 209 | 210 | [Abram Hindle](https://github.com/abramhindle/) is responsible for getting 211 | a web version of this utility online (based on his 212 | [ttwl-cli-saveedit](https://github.com/abramhindle/ttwl-cli-saveedit/) 213 | fork for Wonderlands). Thanks! 214 | 215 | Many thanks also to Baysix, who endured an awful lot of basic questions about 216 | pulling apart item serial numbers. Without their help, we wouldn't have 217 | item level editing (or nice item names in the output)! 218 | 219 | Basically what I'm saying is that anything remotely "hard" in here is all thanks 220 | to lots of other folks. I'm just pasting together all their stuff. Thanks, all! 221 | 222 | # License 223 | 224 | All code in this project is licensed under the 225 | [zlib/libpng license](https://opensource.org/licenses/Zlib). A copy is 226 | provided in [COPYING.txt](COPYING.txt). 227 | 228 | # Other Utilities 229 | 230 | Various BL3 Savegame/Profile editors have been popping up, ever since Gibbed 231 | released the encryption details. Here's a few which could be more to your 232 | liking, if you didn't want to use this one for whatever reason: 233 | 234 | - [FromDarkHell's BL3 Save/Profile Editor](https://github.com/FromDarkHell/BL3SaveEditor) - 235 | Written in C#, has EXE downloads for ease of use on Windows. 236 | - [Zak's Borderlands 3 Save Editor](https://github.com/ZakisM/bl3_save_edit) - Written 237 | in Rust, and is cross-platform. Edits both saves and profiles. 238 | - [Raptor's Editor](https://bl3.swiss.dev/) - This is a web-fronted editor, 239 | though it uses a [local executable](https://github.com/cfi2017/bl3-save/releases) 240 | to do all the work. Sourcecode is available, and it works on Windows, Mac, 241 | and Linux. Coded in [Go](https://golang.org/). 242 | 243 | A couple others exist but as of September 2022, are out of date and not really great 244 | for use on newer saves. Here they are, though, in case they get updated without 245 | me noticing: 246 | 247 | - [HackerSmaker's CSave Editor](https://github.com/HackerSmacker/CSave) - Cross-platform 248 | commandline editor written in C. Has a terminal (ncurses) UI on UNIX-like OSes. 249 | - [sandsmark's borderlands3-save-editor](https://github.com/sandsmark/borderlands3-save-editor) - 250 | Written in C++ with Qt for GUI. Is still in development. Native downloads 251 | for Windows, but should compile fine on other platforms. 252 | 253 | In Memoriam: Baysix, the author of the original web-based editor at bl3editor.com, 254 | passed away in early 2021, and that editor is now permanently offline. RIP! 255 | 256 | # Changelog 257 | 258 | **v1.19.0** - *(unreleased)* 259 | - Updated protobuf minimum version to 5.27.3 - **NOTE:** This needs some more 260 | regression testing! 261 | - Updated profile protobuf with settings addition from 2024-08-08 patch 262 | *(this doesn't actually really affect the application at all)* 263 | 264 | **v1.18.0** - July 19, 2024 265 | - Added new movie-related cosmetics introduced in the July 18, 2024 patch 266 | - Python 3.12 Compatibility: Replaced `pkg_resources` usage with 267 | `importlib.resources` equivalents. 268 | - Minimum Python version increased to 3.9 269 | 270 | **v1.17.0** - September 1, 2023 271 | - Added `--copy-tvhm` option to copy TVHM state to NVHM. 272 | - Ported `--inventory-wipe` inventory wipe option from `ttwl-cli-saveedit` 273 | - Added new cosmetics introduced in the Aug 31, 2023 patch 274 | 275 | **v1.16.0** - November 18, 2021 276 | - Updated name of "Seventh Sense (unknown version)" to "Seventh Sense (purple 277 | version)", since realizing that it's now a valid drop from the Diamond 278 | Armory (presumably since DLC5's release). 279 | - Updated for Vault Card 3 patch: 280 | - Data Updates (to understand new gear) 281 | - Vault Card 3 Keys/Chests 282 | 283 | **v1.15.1** - August 11, 2021 284 | - Updated with some Character Heads which were shipped with the Vault Card 2 285 | update but overlooked for 1.15.0. 286 | 287 | **v1.15.0** - August 5, 2021 288 | - Updated for Vault Card 2 patch: 289 | - Data Updates (to understand new gear) 290 | - Vault Card 2 Keys/Chests 291 | - Updated Bank SDU count 292 | 293 | **v1.14.0** - July 7, 2021 294 | - Added `--mission-paths` to `bl3-save-info` 295 | - Added `--delete-pt1-mission` and `--delete-pt2-mission` to `bl3-save-edit` 296 | - Added options to clear seasonal event challenge progress (Bloody Harvest, 297 | Broken Hearts, Revenge of the Cartels) 298 | 299 | **v1.13.0** - June 24, 2021 300 | - Updated for today's patch 301 | - Updated level cap to 72 302 | - Updated protobufs 303 | - Added `--reverse` option for my Savegame Archive processing script, for some more 304 | flexibility on the CSS when outputting HTML 305 | 306 | **v1.12.0** - April 11, 2021 307 | - Updated with DLC6 (Director's Cut) compatibility. 308 | - Support for updated item serial number format 309 | - Added `--guardian-keys`, `--vaultcard1-keys`, and `--vaultcard1-chests` arguments 310 | for profile editor 311 | - Fixed a few item names which weren't being mapped properly 312 | 313 | **v1.11.1** - February 12, 2021 314 | - Fixed name of the Broken Hearts 2021 weapon trinket "Cosmic Romance" to be "Tentacle 315 | Ventricles" like in game -- it was updated with an OakTMS update on Feb 11 by GBX. 316 | 317 | **v1.11.0** - February 1, 2021 318 | - Added Citizen Science editing functionality to profile editor, thanks 319 | to [@apple1417](https://github.com/apple1417/) 320 | 321 | **v1.10.4** - January 21, 2021 322 | - Updated with the new cosmetics data from today's patch. *(Totally forgot to do that 323 | in v1.10.3.)* 324 | 325 | **v1.10.3** - January 21, 2021 326 | - Updated internal inventory DB with data from today's patch. (No real functionality 327 | changes with this.) 328 | 329 | **v1.10.2** - November 10, 2020 330 | - Updated internal inventory DB with a minor change which was in the previous GBX 331 | patch. (Probably no actual functionality changes with this.) 332 | 333 | **v1.10.1** - November 10, 2020 334 | - A second patch was released, about 12 hours after the Designer's Cut release, which 335 | updated the protobufs and was causing us to not read saves properly. This has been 336 | fixed up now. 337 | - Also require at least protobuf v3.12, which appears to be required as of my 338 | latest protobuf generation. 339 | 340 | **v1.10.0** - November 9, 2020 341 | - Updated with DLC5 (Designer's Cut) support 342 | 343 | **v1.9.2** - October 8, 2020 344 | - Added in the missed weapon skin "Porphyrophobia," from Bloody Harvest 2020. 345 | 346 | **v1.9.1** - September 30, 2020 347 | - Fixed unlocking FL4K's "Antisoci4l" head (though you should still get 348 | that one [all legit-like](https://gearboxloot.com/products/borderlands-3-mask-skins-head-pack)) 349 | 350 | **v1.9.0** - September 10, 2020 351 | - Updated with DLC4 (Psycho Krieg and the Fantastic Fustercluck) support 352 | 353 | **v1.8.2** - July 27, 2020 354 | - Updated to allow unlocking the Battle Driver and Devil Tooth weapon trinkets, since 355 | the July 23 patch fixed those so that they properly stay in your profile. 356 | 357 | **v1.8.1** - July 14, 2020 358 | - `--item-mayhem-max` and `--item-mayhem-levels` will now apply Mayhem parts to 359 | grenades as well as weapons. 360 | 361 | **v1.8.0** - July 11, 2020 362 | - Added `--mayhem-seed` option to `bl3-save-edit`, to set the random seed used to 363 | determine active Mayhem modifiers. The seed is now shown in the `bl3-save-info` 364 | output, as well. 365 | 366 | **v1.7.2** - July 6, 2020 367 | - Allow characters to use skill trees properly if levelled higher directly from level 1. 368 | 369 | **v1.7.1** - June 29, 2020 370 | - Added three more DLC3 room decorations that I'd missed orginally, for the profile 371 | editor's cosmetic-unlock ability 372 | - Added Jetbeast unlocks (both parts and skins) 373 | 374 | **v1.7.0** - June 26, 2020 375 | - Updated for DLC3 (Bounty of Blood) content 376 | - Jetbeast vehicle unlocks are forthcoming, until I can actually test them out. 377 | - Added various Guardian Rank processing 378 | - For savegames and profiles, added `--zero-guardian-rank` 379 | - For only profiles, added `--min-guardian-rank`, `--guardian-rank-rewards`, and 380 | `--guardian-rank-tokens` 381 | - Added `--unlock cubepuzzle` to reset the Eridian Cube puzzle in Desolation's Edge 382 | - Added `--clear-takedowns` to get rid of the Takedown mission-pickup notifications 383 | for chars you never intend to do Takedowns with. 384 | - Item name processing will now recognize the specific legendary artifact balances 385 | added by the Maliwan Takedown update (they're functionally identical to the world- 386 | drop versions) 387 | - Updated `bl3-process-archive-saves` to wipe out Guardian Rank entirely 388 | 389 | **v1.6.2** - June 18, 2020 390 | - Allow setting character/item levels higher than the currently-known max, in case 391 | users want to pre-level their saves ahead of level cap increases (and in case 392 | they don't want to wait for an official update of this app) 393 | 394 | **v1.6.1** - June 13, 2020 395 | - Updated room decoration/trinket/weaponskin alphabetization to ignore case (so 396 | "Casino Banner" sorts before "COV Bat," for instance). 397 | - Counts of unlocked cosmetic items in `bl3-profile-info` will now include the 398 | "default" cosmetics which are always unlocked, so those counts match the numbers 399 | in-game. 400 | 401 | **v1.6.0** - June 11, 2020 402 | - Updated for extra content introduced by Takedown at the Guardian Breach 403 | - Added `--golden-keys` option to `bl3-profile-edit`, to set available Golden Keys 404 | - Added `--randomize-guid` option to `bl3-save-edit` 405 | 406 | **v1.5.2** - May 23, 2020 407 | - Updated item name mapping to include the COM Balances used by Trials bosses, for 408 | their dedicated COM drops. 409 | - Add in the `--csv` option to allow importing/exporting items into CSV files, 410 | instead of just text files (which is the default without `--csv`). Has no effect 411 | except on item imports/exports. 412 | 413 | **v1.5.1** - May 5, 2020 414 | - Fixed alphabetization of COV Buzzaxe room decoration. In the game data it looks 415 | like it's called "Psycho Buzzaxe," but the actual in-game label is COV Buzzaxe. 416 | - Fixed name displays for "regular" (non-E-Tech, non-Legendary) Maliwan snipers and 417 | Tediore pistols. 418 | - Updated item name mapping to distinguish between the blue-rarity mission reward 419 | version of Redistributor from the legendary version from Maliwan Takedown. 420 | - Updated `bl3-process-archive-saves` to clear out some 3rd-playthrough data 421 | which earlier versions had accidentally introduced *(only really of interest 422 | to myself)* 423 | 424 | **v1.5.0** - April 28, 2020 425 | - Added ability to alter Mayhem level for items, both in savegames and profile. 426 | `--item-mayhem-max` will set items to the max Mayhem level (`10`), or 427 | `--item-mayhem-levels ` will set a specific level (`0` to remove Mayhem 428 | from items entirely). 429 | - Item level reports when viewing contents or exporting items will include 430 | the Mayhem levels 431 | - Info views will show the total number of available SDUs along with the 432 | purchased count 433 | - Various internal code reorganization 434 | 435 | **v1.4.0** - April 23, 2020 436 | - Updated for the Cartels + Mayhem 2.0 patch: 437 | - Update item/weapon handling to understand new serial number versions 438 | - Added in new cosmetic unlocks 439 | - Mayhem level can now be set as high as 10 440 | - Extra SDU levels added 441 | - Fixed a bug related to viewing vehicle unlocks, thanks to new game data 442 | - Introduction of profile editing utilities, via `bl3-profile-edit`, 443 | `bl3-profile-info`, `bl3-profile-import-json`, and `bl3-profile-import-protobuf`. 444 | The following functionality is supported: 445 | - Importing/Exporting to both JSON and Protobuf 446 | - Importing/Exporting items to/from the bank 447 | - Altering the level of gear stored in the bank 448 | - Unlocking all global customizations/cosmetics 449 | - Alphabetizing the list of room decorations, trinkets, and weapon skins 450 | - Display changes for `bl3-save-info` - introduced individual arguments to 451 | show more detailed information as-requested, instead of only having a 452 | global `-v`/`--verbose` option: 453 | - Added `-i`/`--items` argument, to show inventory information 454 | - Added `--fast-travel` argument, to show unlocked fast travel stations 455 | - Added `--all-challenges` argument, to list all challenges in the save file 456 | and their statuses (note: this will be over 1.5k items!) 457 | - Added `--all-missions` argument, to list all completed missions in addition 458 | to active ones 459 | - `-v`/`--verbose` now implies the four options above. 460 | - `bl3-save-info` will now also report on whether the savegame has finished the 461 | main game or any of the DLCs. 462 | - Added `--unfinish-nvhm` option to `bl3-save-edit`, to completely clear out 463 | any TVHM data and pretend that NVHM was never finished. 464 | 465 | **v1.3.1** - April 14, 2020 466 | - Bah, forgot a few more README tweaks about the JSON export/import 467 | 468 | **v1.3.0** - April 14, 2020 469 | - Added `json` output type, to export the protobuf as encoded into JSON 470 | - Added `bl3-save-import-json` utility, to load JSON into a savegame 471 | 472 | **v1.2.2** - April 12, 2020 473 | - Updated README with some more specific Windows 10 installation advice. 474 | 475 | **v1.2.1** - April 12, 2020 476 | - Updated Credits section of the README with one more credit that I'd 477 | wanted to put in but forgot. :) 478 | 479 | **v1.2.0** - April 12, 2020 480 | - Added ability to change item/weapon levels, using `--items-to-char` 481 | or `--item-levels` 482 | - Item Imports will now *not* import Fabricators unless explicitly 483 | told to with the `--allow-fabricator` option. 484 | - Item names and levels are now shown where appropriate: 485 | - In verbose `bl3-save-info` output 486 | - In item export files 487 | - While importing items 488 | - Updated to Protobuf v3, which is what BL3 itself uses. Now when we 489 | re-save without any edits, the save should be identical to the 490 | original savegame. 491 | - Item export will now be done without any encryption, so an item will 492 | have the exact same item code regardless of where it came from 493 | (previously, item codes would change every time the game was saved, 494 | so the same item could have very different-looking codes) 495 | - Added `-V`/`--version` flag to show version number 496 | 497 | **v1.1.1** - April 7, 2020 498 | - Added in Citizen Science mission name. 499 | 500 | **v1.1.0** - April 7, 2020 501 | - Added `bl3-save-import-protobuf` command, to load a raw protobuf file 502 | into an existing savegame. 503 | 504 | **v1.0.1** - April 5, 2020 505 | - Some saves include Eridium as an ammo type, presumably related to the 506 | Fabricator. Fixed a crash in `bl3-save-info` related to that. 507 | 508 | **v1.0.0** - April 5, 2020 509 | - Initial version 510 | 511 | -------------------------------------------------------------------------------- /bl3save/OakProfile_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # NO CHECKED-IN PROTOBUF GENCODE 4 | # source: OakProfile.proto 5 | # Protobuf Python Version: 5.27.2 6 | """Generated protocol buffer code.""" 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import descriptor_pool as _descriptor_pool 9 | from google.protobuf import runtime_version as _runtime_version 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf.internal import builder as _builder 12 | _runtime_version.ValidateProtobufRuntimeVersion( 13 | _runtime_version.Domain.PUBLIC, 14 | 5, 15 | 27, 16 | 2, 17 | '', 18 | 'OakProfile.proto' 19 | ) 20 | # @@protoc_insertion_point(imports) 21 | 22 | _sym_db = _symbol_database.Default() 23 | 24 | 25 | from . import OakShared_pb2 as OakShared__pb2 26 | 27 | 28 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10OakProfile.proto\x12\x07OakSave\x1a\x0fOakShared.proto\"H\n\x19PlayerInputBinding_Button\x12\x18\n\x10rebind_data_path\x18\x01 \x01(\t\x12\x11\n\tkey_names\x18\x02 \x03(\t\"P\n\x1bPlayerInputBinding_Axis_Key\x12\x10\n\x08key_name\x18\x01 \x01(\t\x12\x1f\n\x08scale_3d\x18\x02 \x01(\x0b\x32\r.OakSave.Vec3\"g\n\x17PlayerInputBinding_Axis\x12\x18\n\x10rebind_data_path\x18\x01 \x01(\t\x12\x32\n\x04keys\x18\x02 \x03(\x0b\x32$.OakSave.PlayerInputBinding_Axis_Key\"\xca\x01\n\x1bPlayerInputBinding_Category\x12\x1a\n\x12\x63\x61tegory_data_path\x18\x01 \x01(\t\x12\x19\n\x11\x63ontext_data_path\x18\x02 \x01(\t\x12;\n\x0f\x62utton_bindings\x18\x03 \x03(\x0b\x32\".OakSave.PlayerInputBinding_Button\x12\x37\n\raxis_bindings\x18\x04 \x03(\x0b\x32 .OakSave.PlayerInputBinding_Axis\"O\n\x13PlayerInputBindings\x12\x38\n\ncategories\x18\x01 \x03(\x0b\x32$.OakSave.PlayerInputBinding_Category\"T\n!OakProfileLastInventoryFilterInfo\x12\x14\n\x0cslot_type_id\x18\x01 \x01(\t\x12\x19\n\x11last_filter_index\x18\x02 \x01(\x05\"}\n\x1aOakProfileMenuTutorialInfo\x12\x16\n\x0eseen_tutorials\x18\x01 \x03(\t\x12\x1a\n\x12tutorials_disabled\x18\x02 \x01(\x08\x12+\n#tutorials_allowed_in_non_game_modes\x18\x03 \x01(\x08\"M\n\x16OakFriendEncounterData\x12\x16\n\x0enum_encounters\x18\x01 \x01(\r\x12\x1b\n\x13time_last_encounter\x18\x02 \x01(\x03\"o\n\x14GearSoldByFriendData\x12\x1a\n\x12gear_serial_number\x18\x01 \x01(\t\x12$\n\x1cplayer_class_identifier_hash\x18\x02 \x01(\x05\x12\x15\n\rfriend_net_id\x18\x03 \x01(\t\"N\n\x1eGuardianRankRewardSaveGameData\x12\x12\n\nnum_tokens\x18\x01 \x01(\x05\x12\x18\n\x10reward_data_path\x18\x02 \x01(\t\"\xec\x01\n\x17GuardianRankProfileData\x12\x18\n\x10\x61vailable_tokens\x18\x01 \x01(\x05\x12=\n\x0crank_rewards\x18\x02 \x03(\x0b\x32\'.OakSave.GuardianRankRewardSaveGameData\x12\x15\n\rguardian_rank\x18\x03 \x01(\x05\x12\x1b\n\x13guardian_experience\x18\x04 \x01(\x05\x12#\n\x1bguardian_reward_random_seed\x18\x05 \x01(\x05\x12\x1f\n\x17new_guardian_experience\x18\x06 \x01(\x03\"l\n\x11RecentlyMetPlayer\x12\x17\n\x0fshift_player_id\x18\x01 \x01(\t\x12\x1d\n\x15\x66irst_party_player_id\x18\x02 \x01(\t\x12\x1f\n\x17show_shift_player_entry\x18\x03 \x01(\x08\"\x81;\n\x07Profile\x12\x19\n\x11\x65nable_aim_assist\x18\x01 \x01(\x08\x12\x1b\n\x13gamepad_invert_look\x18\x02 \x01(\x08\x12\x1b\n\x13gamepad_invert_turn\x18\x03 \x01(\x08\x12\x1b\n\x13gamepad_invert_move\x18\x04 \x01(\x08\x12\x1d\n\x15gamepad_invert_strafe\x18\x05 \x01(\x08\x12\x18\n\x10\x65nable_vibration\x18\x06 \x01(\x08\x12\x1a\n\x12invert_mouse_pitch\x18\x07 \x01(\x08\x12\x1e\n\x16\x65nable_mouse_smoothing\x18\x08 \x01(\x08\x12\x13\n\x0bmouse_scale\x18\t \x01(\x02\x12\x1b\n\x13show_damage_numbers\x18\n \x01(\x08\x12 \n\x18show_damage_number_icons\x18\x0b \x01(\x08\x12 \n\x18\x65nable_training_messages\x18\x0c \x01(\x08\x12\x16\n\x0eshow_text_chat\x18\r \x01(\x08\x12\x18\n\x10\x63\x65nter_crosshair\x18\x0e \x01(\x08\x12\x15\n\rtoggle_sprint\x18\x0f \x01(\x08\x12\x15\n\rtoggle_crouch\x18\x10 \x01(\x08\x12\x16\n\x0e\x63\x65nsor_content\x18\x11 \x01(\x08\x12\x14\n\x0cmusic_volume\x18\x12 \x01(\x02\x12\x1c\n\x14sound_effects_volume\x18\x13 \x01(\x02\x12\x11\n\tvo_volume\x18\x14 \x01(\x02\x12\x14\n\x0cvoice_volume\x18\x15 \x01(\x02\x12\x1a\n\x12\x65nable_optional_vo\x18\x16 \x01(\x08\x12\x14\n\x0cpush_to_talk\x18\x17 \x01(\x08\x12\x1f\n\x17\x65nable_controller_audio\x18\x18 \x01(\x08\x12\x1b\n\x13speaker_angle_front\x18\x19 \x01(\x02\x12\x1a\n\x12speaker_angle_side\x18\x1a \x01(\x02\x12\x1a\n\x12speaker_angle_back\x18\x1b \x01(\x02\x12\x15\n\rspeaker_setup\x18\x1c \x01(\r\x12 \n\x18mute_audio_on_focus_loss\x18\x1d \x01(\x08\x12#\n\x1bhide_strict_nat_help_dialog\x18\" \x01(\x08\x12;\n\x15player_input_bindings\x18# \x01(\x0b\x32\x1c.OakSave.PlayerInputBindings\x12\x13\n\x0bnews_hashes\x18$ \x03(\r\x12\x1d\n\x15last_used_savegame_id\x18% \x01(\r\x12%\n\x1dgamepad_hip_sensitivity_level\x18& \x01(\x05\x12(\n gamepad_zoomed_sensitivity_level\x18\' \x01(\x05\x12)\n!gamepad_vehicle_sensitivity_level\x18( \x01(\x05\x12$\n\x1cgamepad_movement_dead_zone_x\x18) \x01(\x02\x12$\n\x1cgamepad_movement_dead_zone_y\x18* \x01(\x02\x12&\n\x1egamepad_look_dead_zone_inner_x\x18+ \x01(\x02\x12&\n\x1egamepad_look_dead_zone_outer_x\x18, \x01(\x02\x12&\n\x1egamepad_look_dead_zone_inner_y\x18- \x01(\x02\x12&\n\x1egamepad_look_dead_zone_outer_y\x18. \x01(\x02\x12,\n$gamepad_vehicle_movement_dead_zone_x\x18/ \x01(\x02\x12,\n$gamepad_vehicle_movement_dead_zone_y\x18\x30 \x01(\x02\x12.\n&gamepad_vehicle_look_dead_zone_inner_x\x18\x31 \x01(\x02\x12.\n&gamepad_vehicle_look_dead_zone_outer_x\x18\x32 \x01(\x02\x12.\n&gamepad_vehicle_look_dead_zone_inner_y\x18\x33 \x01(\x02\x12.\n&gamepad_vehicle_look_dead_zone_outer_y\x18\x34 \x01(\x02\x12$\n\x1cgamepad_left_dead_zone_inner\x18\x35 \x01(\x02\x12$\n\x1cgamepad_left_dead_zone_outer\x18\x36 \x01(\x02\x12%\n\x1dgamepad_right_dead_zone_inner\x18\x37 \x01(\x02\x12%\n\x1dgamepad_right_dead_zone_outer\x18\x38 \x01(\x02\x12*\n\"gamepad_look_axial_dead_zone_scale\x18\x39 \x01(\x02\x12*\n\"gamepad_move_axial_dead_zone_scale\x18: \x01(\x02\x12-\n%gamepad_use_advanced_hip_aim_settings\x18; \x01(\x08\x12\x30\n(gamepad_use_advanced_zoomed_aim_settings\x18< \x01(\x08\x12\x31\n)gamepad_use_advanced_vehicle_aim_settings\x18= \x01(\x08\x12\x1c\n\x14gamepad_hip_yaw_rate\x18> \x01(\x02\x12\x1e\n\x16gamepad_hip_pitch_rate\x18? \x01(\x02\x12\x1d\n\x15gamepad_hip_extra_yaw\x18@ \x01(\x02\x12\x1f\n\x17gamepad_hip_extra_pitch\x18\x41 \x01(\x02\x12 \n\x18gamepad_hip_ramp_up_time\x18\x42 \x01(\x02\x12!\n\x19gamepad_hip_ramp_up_delay\x18\x43 \x01(\x02\x12\x1f\n\x17gamepad_zoomed_yaw_rate\x18\x44 \x01(\x02\x12!\n\x19gamepad_zoomed_pitch_rate\x18\x45 \x01(\x02\x12 \n\x18gamepad_zoomed_extra_yaw\x18\x46 \x01(\x02\x12\"\n\x1agamepad_zoomed_extra_pitch\x18G \x01(\x02\x12#\n\x1bgamepad_zoomed_ramp_up_time\x18H \x01(\x02\x12$\n\x1cgamepad_zoomed_ramp_up_delay\x18I \x01(\x02\x12 \n\x18gamepad_vehicle_yaw_rate\x18J \x01(\x02\x12\"\n\x1agamepad_vehicle_pitch_rate\x18K \x01(\x02\x12!\n\x19gamepad_vehicle_extra_yaw\x18L \x01(\x02\x12#\n\x1bgamepad_vehicle_extra_pitch\x18M \x01(\x02\x12$\n\x1cgamepad_vehicle_ramp_up_time\x18N \x01(\x02\x12%\n\x1dgamepad_vehicle_ramp_up_delay\x18O \x01(\x02\x12\x1c\n\x14ironsight_aim_assist\x18P \x01(\x08\x12\x1f\n\x17walking_joystick_scheme\x18Q \x01(\r\x12\x1f\n\x17\x64riving_joystick_scheme\x18R \x01(\r\x12\x17\n\x0fmouse_ads_scale\x18S \x01(\x02\x12\x1b\n\x13mouse_vehicle_scale\x18T \x01(\x02\x12\"\n\x1amouse_ironsight_aim_assist\x18U \x01(\x08\x12\x1a\n\x12vehicle_input_mode\x18V \x01(\r\x12\x19\n\x11weapon_aim_toggle\x18W \x01(\x08\x12\x1e\n\x16mantle_requires_button\x18X \x01(\x08\x12\x1e\n\x16\x66ixed_minimap_rotation\x18Y \x01(\x08\x12\x18\n\x10map_invert_pitch\x18Z \x01(\x08\x12\x16\n\x0emap_invert_yaw\x18[ \x01(\x08\x12\x12\n\ndifficulty\x18\\ \x01(\r\x12 \n\x18swap_dual_wield_controls\x18] \x01(\x08\x12\x10\n\x08\x62\x61se_fov\x18^ \x01(\x02\x12%\n\x1d\x63rosshair_neutral_color_frame\x18_ \x01(\r\x12#\n\x1b\x63rosshair_enemy_color_frame\x18` \x01(\r\x12\"\n\x1a\x63rosshair_ally_color_frame\x18\x61 \x01(\r\x12\x18\n\x10\x65nable_subtitles\x18\x62 \x01(\x08\x12\x1e\n\x16\x65nable_closed_captions\x18\x63 \x01(\x08\x12\x1d\n\x15last_status_menu_page\x18\x64 \x01(\t\x12P\n\x1cinventory_screen_last_filter\x18\x65 \x03(\x0b\x32*.OakSave.OakProfileLastInventoryFilterInfo\x12:\n\rtutorial_info\x18\x66 \x01(\x0b\x32#.OakSave.OakProfileMenuTutorialInfo\x12\x1c\n\x14\x64\x65\x66\x61ult_network_type\x18g \x01(\r\x12\x1b\n\x13\x64\x65\x66\x61ult_invite_type\x18h \x01(\r\x12\x1a\n\x12matchmaking_region\x18i \x01(\t\x12\x19\n\x11streaming_service\x18j \x01(\r\x12 \n\x18max_cached_friend_events\x18k \x01(\x05\x12\"\n\x1amax_cached_friend_statuses\x18l \x01(\x05\x12\x15\n\rfriend_events\x18m \x03(\t\x12\x17\n\x0f\x66riend_statuses\x18n \x03(\t\x12&\n\x1elast_whisper_fetch_events_time\x18o \x01(\x03\x12(\n last_whisper_fetch_statuses_time\x18p \x01(\x03\x12\x1f\n\x17\x64\x65sired_crossplay_state\x18q \x01(\r\x12\x42\n\x11\x66riend_encounters\x18\x85\x01 \x03(\x0b\x32&.OakSave.Profile.FriendEncountersEntry\x12\"\n\x19max_friend_encounter_size\x18\x86\x01 \x01(\x05\x12:\n\x12profile_stats_data\x18\x87\x01 \x03(\x0b\x32\x1d.OakSave.GameStatSaveGameData\x12I\n\x1c\x62\x61nk_inventory_category_list\x18\x88\x01 \x03(\x0b\x32\".OakSave.InventoryCategorySaveData\x12\x1c\n\x13\x62\x61nk_inventory_list\x18\x89\x01 \x03(\x0c\x12!\n\x18lost_loot_inventory_list\x18\x8a\x01 \x03(\x0c\x12-\n\x0enpc_mail_items\x18\x8b\x01 \x03(\x0b\x32\x14.OakSave.OakMailItem\x12\x13\n\nmail_guids\x18\x8c\x01 \x03(\t\x12\x1a\n\x11unread_mail_guids\x18\x8d\x01 \x03(\t\x12<\n\x14gear_sold_by_friends\x18\x8e\x01 \x03(\x0b\x32\x1d.OakSave.GearSoldByFriendData\x12\x36\n\x10profile_sdu_list\x18\x8f\x01 \x03(\x0b\x32\x1b.OakSave.OakSDUSaveGameData\x12G\n\x17unlocked_customizations\x18\x90\x01 \x03(\x0b\x32%.OakSave.OakCustomizationSaveGameData\x12[\n&unlocked_inventory_customization_parts\x18\x91\x01 \x03(\x0b\x32*.OakSave.OakInventoryCustomizationPartInfo\x12\x38\n\rguardian_rank\x18\x92\x01 \x01(\x0b\x32 .OakSave.GuardianRankProfileData\x12\\\n\"unlocked_crew_quarters_decorations\x18\x93\x01 \x03(\x0b\x32/.OakSave.CrewQuartersDecorationItemSaveGameData\x12P\n\x1cunlocked_crew_quarters_rooms\x18\x94\x01 \x03(\x0b\x32).OakSave.CrewQuartersRoomItemSaveGameData\x12\"\n\x19\x65nable_mouse_acceleration\x18\x96\x01 \x01(\x08\x12\x1d\n\x14\x65nable_gamepad_input\x18\x97\x01 \x01(\x08\x12\"\n\x19use_classic_gamepad_input\x18\x98\x01 \x01(\x08\x12\x16\n\rmaster_volume\x18\x99\x01 \x01(\x02\x12\x1d\n\x14monitor_display_type\x18\x9a\x01 \x01(\r\x12\x16\n\rgraphics_mode\x18\x9b\x01 \x01(\r\x12\x19\n\x10\x66rame_rate_limit\x18\x9c\x01 \x01(\r\x12\x19\n\x10\x62\x61se_vehicle_fov\x18\x9d\x01 \x01(\x02\x12\x19\n\x10graphics_quality\x18\x9e\x01 \x01(\r\x12\x1e\n\x15\x61nisotropic_filtering\x18\x9f\x01 \x01(\r\x12\x17\n\x0eshadow_quality\x18\xa0\x01 \x01(\r\x12\"\n\x19\x64isplay_performance_stats\x18\xa1\x01 \x01(\r\x12\x17\n\x0etexture_detail\x18\xa2\x01 \x01(\r\x12\x16\n\rdraw_distance\x18\xa3\x01 \x01(\r\x12\x10\n\x07\x63lutter\x18\xa4\x01 \x01(\r\x12\x15\n\x0ctessellation\x18\xa5\x01 \x01(\r\x12\x10\n\x07\x66oliage\x18\xa6\x01 \x01(\r\x12\x18\n\x0f\x66oliage_shadows\x18\xa7\x01 \x01(\x08\x12\x1b\n\x12planar_reflections\x18\xa8\x01 \x01(\x08\x12\x17\n\x0evolumetric_fog\x18\xa9\x01 \x01(\r\x12!\n\x18screen_space_reflections\x18\xaa\x01 \x01(\r\x12!\n\x18\x63haracter_texture_detail\x18\xab\x01 \x01(\r\x12\x19\n\x10\x63haracter_detail\x18\xac\x01 \x01(\r\x12\"\n\x19\x61mbient_occlusion_quality\x18\xad\x01 \x01(\r\x12\x1b\n\x12object_motion_blur\x18\xae\x01 \x01(\x08\x12\x13\n\nlens_flare\x18\xaf\x01 \x01(\x08\x12\"\n\x19\x63ombat_number_long_format\x18\xb0\x01 \x01(\x08\x12!\n\x18show_minimap_legendaries\x18\xb1\x01 \x01(\x08\x12\x1c\n\x13use_player_callouts\x18\xb2\x01 \x01(\x08\x12+\n\"friend_event_notification_lifetime\x18\xb3\x01 \x01(\r\x12,\n#friend_event_notification_frequency\x18\xb4\x01 \x01(\r\x12%\n\x1ctrade_request_reception_type\x18\xb5\x01 \x01(\r\x12\x17\n\x0ehead_bob_scale\x18\xb6\x01 \x01(\x02\x12\x1c\n\x13has_seen_first_boot\x18\xb8\x01 \x01(\x08\x12\x15\n\x0csubs_cc_size\x18\xbd\x01 \x01(\x02\x12#\n\x1a\x63\x63_subs_background_opacity\x18\xbe\x01 \x01(\x02\x12\x1e\n\x15walking_button_scheme\x18\xbf\x01 \x01(\r\x12\x1e\n\x15\x64riving_button_scheme\x18\xc0\x01 \x01(\r\x12\x13\n\nglyph_mode\x18\xc1\x01 \x01(\r\x12\x10\n\x07use_MPH\x18\xc2\x01 \x01(\x08\x12Z\n$registered_downloadable_entitlements\x18\xc3\x01 \x03(\x0b\x32+.OakSave.RegisteredDownloadableEntitlements\x12\x18\n\x0fseen_news_items\x18\xc4\x01 \x03(\t\x12\x1f\n\x16\x61uto_centering_enabled\x18\xc5\x01 \x01(\x08\x12)\n increased_chance_for_subscribers\x18\xc6\x01 \x01(\x08\x12!\n\x18rare_chest_event_enabled\x18\xc7\x01 \x01(\x08\x12\x1d\n\x14\x62\x61\x64\x61ss_event_enabled\x18\xc8\x01 \x01(\x08\x12\x1d\n\x14pinata_event_enabled\x18\xc9\x01 \x01(\x08\x12\'\n\x1emin_time_between_badass_events\x18\xca\x01 \x01(\x05\x12\x1d\n\x14hud_scale_multiplier\x18\xcb\x01 \x01(\x02\x12\x39\n0disable_spatial_audio__or__has_reset_console_fov\x18\xcc\x01 \x01(\x08\x12\x1f\n\x16total_playtime_seconds\x18\xcd\x01 \x01(\x05\x12#\n\x1amoxxis_drink_event_enabled\x18\xce\x01 \x01(\x08\x12+\n\"moxxis_drink_event_bits_product_id\x18\xcf\x01 \x01(\x05\x12\x37\n\x0e\x63hallenge_data\x18\xd0\x01 \x03(\x0b\x32\x1e.OakSave.ChallengeSaveGameData\x12\'\n\x1e\x43itizenScienceLevelProgression\x18\xd1\x01 \x03(\x05\x12(\n\x1f\x64\x65\x66\x61ult_dead_zone_inner_updated\x18\xd2\x01 \x01(\x08\x12\x1e\n\x15\x64isable_event_content\x18\xd3\x01 \x01(\x08\x12\"\n\x19\x64\x65sired_friend_sync_state\x18\xd4\x01 \x01(\r\x12\x1f\n\x16needs_shift_first_boot\x18\xd5\x01 \x01(\x08\x12\x39\n\x14recently_met_players\x18\xd6\x01 \x03(\x0b\x32\x1a.OakSave.RecentlyMetPlayer\x12)\n CitizenScienceActiveBoosterIndex\x18\xd7\x01 \x01(\x05\x12\x31\n(CitizenScienceActiveBoosterRemainingTime\x18\xd8\x01 \x01(\x02\x12-\n$CitizenScienceActiveBoosterTotalTime\x18\xd9\x01 \x01(\x02\x12*\n!StreamerPrimaryActiveBoosterIndex\x18\xda\x01 \x01(\x05\x12\x32\n)StreamerPrimaryActiveBoosterRemainingTime\x18\xdb\x01 \x01(\x02\x12.\n%StreamerPrimaryActiveBoosterTotalTime\x18\xdc\x01 \x01(\x02\x12,\n#StreamerSecondaryActiveBoosterIndex\x18\xdd\x01 \x01(\x05\x12\x34\n+StreamerSecondaryActiveBoosterRemainingTime\x18\xde\x01 \x01(\x02\x12\x30\n\'StreamerSecondaryActiveBoosterTotalTime\x18\xdf\x01 \x01(\x02\x12\x1c\n\x13StreamerBoosterTier\x18\xe0\x01 \x01(\x05\x12$\n\x1b\x43itizenScienceCSBucksAmount\x18\xe2\x01 \x01(\x05\x12)\n bCitizenScienceHasSeenIntroVideo\x18\xe3\x01 \x01(\x08\x12$\n\x1b\x62\x43itizenScienceTutorialDone\x18\xe4\x01 \x01(\x08\x12 \n\x17\x65nable_trigger_feedback\x18\xe5\x01 \x01(\x08\x12\'\n\x1e\x66ixed_initial_zonemap_rotation\x18\xe6\x01 \x01(\x08\x12\x33\n\nvault_card\x18\xe7\x01 \x01(\x0b\x32\x1e.OakSave.VaultCardSaveGameData\x12\x1f\n\x16player_selected_league\x18\xe8\x01 \x01(\r\x12\'\n\x1eneeds_shift_first_boot_primary\x18\xe9\x01 \x01(\x08\x12\x18\n\x0f\x61utosell_rarity\x18\xea\x01 \x01(\x05\x1aT\n\x15\x46riendEncountersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12.\n\x05value\x18\x02 \x01(\x0b\x32\x1f.OakSave.OakFriendEncounterDatab\x06proto3') 29 | 30 | _globals = globals() 31 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 32 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'OakProfile_pb2', _globals) 33 | if not _descriptor._USE_C_DESCRIPTORS: 34 | DESCRIPTOR._loaded_options = None 35 | _globals['_PLAYERINPUTBINDING_BUTTON']._serialized_start=46 36 | _globals['_PLAYERINPUTBINDING_BUTTON']._serialized_end=118 37 | _globals['_PLAYERINPUTBINDING_AXIS_KEY']._serialized_start=120 38 | _globals['_PLAYERINPUTBINDING_AXIS_KEY']._serialized_end=200 39 | _globals['_PLAYERINPUTBINDING_AXIS']._serialized_start=202 40 | _globals['_PLAYERINPUTBINDING_AXIS']._serialized_end=305 41 | _globals['_PLAYERINPUTBINDING_CATEGORY']._serialized_start=308 42 | _globals['_PLAYERINPUTBINDING_CATEGORY']._serialized_end=510 43 | _globals['_PLAYERINPUTBINDINGS']._serialized_start=512 44 | _globals['_PLAYERINPUTBINDINGS']._serialized_end=591 45 | _globals['_OAKPROFILELASTINVENTORYFILTERINFO']._serialized_start=593 46 | _globals['_OAKPROFILELASTINVENTORYFILTERINFO']._serialized_end=677 47 | _globals['_OAKPROFILEMENUTUTORIALINFO']._serialized_start=679 48 | _globals['_OAKPROFILEMENUTUTORIALINFO']._serialized_end=804 49 | _globals['_OAKFRIENDENCOUNTERDATA']._serialized_start=806 50 | _globals['_OAKFRIENDENCOUNTERDATA']._serialized_end=883 51 | _globals['_GEARSOLDBYFRIENDDATA']._serialized_start=885 52 | _globals['_GEARSOLDBYFRIENDDATA']._serialized_end=996 53 | _globals['_GUARDIANRANKREWARDSAVEGAMEDATA']._serialized_start=998 54 | _globals['_GUARDIANRANKREWARDSAVEGAMEDATA']._serialized_end=1076 55 | _globals['_GUARDIANRANKPROFILEDATA']._serialized_start=1079 56 | _globals['_GUARDIANRANKPROFILEDATA']._serialized_end=1315 57 | _globals['_RECENTLYMETPLAYER']._serialized_start=1317 58 | _globals['_RECENTLYMETPLAYER']._serialized_end=1425 59 | _globals['_PROFILE']._serialized_start=1428 60 | _globals['_PROFILE']._serialized_end=8981 61 | _globals['_PROFILE_FRIENDENCOUNTERSENTRY']._serialized_start=8897 62 | _globals['_PROFILE_FRIENDENCOUNTERSENTRY']._serialized_end=8981 63 | # @@protoc_insertion_point(module_scope) 64 | -------------------------------------------------------------------------------- /bl3save/OakSave_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # NO CHECKED-IN PROTOBUF GENCODE 4 | # source: OakSave.proto 5 | # Protobuf Python Version: 5.27.2 6 | """Generated protocol buffer code.""" 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import descriptor_pool as _descriptor_pool 9 | from google.protobuf import runtime_version as _runtime_version 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf.internal import builder as _builder 12 | _runtime_version.ValidateProtobufRuntimeVersion( 13 | _runtime_version.Domain.PUBLIC, 14 | 5, 15 | 27, 16 | 2, 17 | '', 18 | 'OakSave.proto' 19 | ) 20 | # @@protoc_insertion_point(imports) 21 | 22 | _sym_db = _symbol_database.Default() 23 | 24 | 25 | from . import OakShared_pb2 as OakShared__pb2 26 | 27 | 28 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rOakSave.proto\x12\x07OakSave\x1a\x0fOakShared.proto\"L\n\x17PlayerClassSaveGameData\x12\x19\n\x11player_class_path\x18\x01 \x01(\t\x12\x16\n\x0e\x64lc_package_id\x18\x02 \x01(\r\"A\n\x18ResourcePoolSavegameData\x12\x0e\n\x06\x61mount\x18\x01 \x01(\x02\x12\x15\n\rresource_path\x18\x02 \x01(\t\"o\n\x12RegionSaveGameData\x12\x12\n\ngame_stage\x18\x01 \x01(\x05\x12\x18\n\x10play_through_idx\x18\x02 \x01(\x05\x12\x13\n\x0bregion_path\x18\x03 \x01(\t\x12\x16\n\x0e\x64lc_package_id\x18\x04 \x01(\r\"\xf8\x01\n\'InventoryBalanceStateInitializationData\x12\x12\n\ngame_stage\x18\x01 \x01(\x05\x12\x16\n\x0einventory_data\x18\x02 \x01(\t\x12\x1e\n\x16inventory_balance_data\x18\x03 \x01(\t\x12\x19\n\x11manufacturer_data\x18\x04 \x01(\t\x12\x11\n\tpart_list\x18\x05 \x03(\t\x12\x19\n\x11generic_part_list\x18\x06 \x03(\t\x12\x17\n\x0f\x61\x64\x64itional_data\x18\x07 \x01(\x0c\x12\x1f\n\x17\x63ustomization_part_list\x18\x08 \x03(\t\"\xd0\x01\n\x1cOakInventoryItemSaveGameData\x12\x1a\n\x12item_serial_number\x18\x01 \x01(\x0c\x12\x1a\n\x12pickup_order_index\x18\x02 \x01(\x05\x12\r\n\x05\x66lags\x18\x03 \x01(\x05\x12\x18\n\x10weapon_skin_path\x18\x04 \x01(\t\x12O\n\x15\x64\x65velopment_save_data\x18\x05 \x01(\x0b\x32\x30.OakSave.InventoryBalanceStateInitializationData\"\x81\x01\n\x1d\x45quippedInventorySaveGameData\x12\x1c\n\x14inventory_list_index\x18\x01 \x01(\x05\x12\x0f\n\x07\x65nabled\x18\x02 \x01(\x08\x12\x16\n\x0eslot_data_path\x18\x03 \x01(\t\x12\x19\n\x11trinket_data_path\x18\x04 \x01(\t\"v\n\x1eOakAbilityTreeItemSaveGameData\x12\x17\n\x0fitem_asset_path\x18\x01 \x01(\t\x12\x0e\n\x06points\x18\x02 \x01(\x05\x12\x12\n\nmax_points\x18\x03 \x01(\x05\x12\x17\n\x0ftree_identifier\x18\x04 \x01(\x05\"Q\n\x1aOakAbilitySlotSaveGameData\x12\x1a\n\x12\x61\x62ility_class_path\x18\x01 \x01(\t\x12\x17\n\x0fslot_asset_path\x18\x02 \x01(\t\"}\n#OakActionAbilityAugmentSaveGameData\x12!\n\x19\x61\x63tion_ability_class_path\x18\x01 \x01(\t\x12\x17\n\x0fslot_asset_path\x18\x02 \x01(\t\x12\x1a\n\x12\x61ugment_asset_path\x18\x03 \x01(\t\"\x9f\x01\n0OakActionAbilityAugmentConfigurationSaveGameData\x12\x1a\n\x12\x61\x62ility_class_path\x18\x01 \x01(\t\x12\x1a\n\x12\x61ugment_asset_path\x18\x02 \x01(\t\x12\x1b\n\x13mod_slot_asset_path\x18\x03 \x01(\t\x12\x16\n\x0emod_asset_path\x18\x04 \x01(\t\"\xf3\x02\n\x1cOakPlayerAbilitySaveGameData\x12\x16\n\x0e\x61\x62ility_points\x18\x01 \x01(\x05\x12?\n\x0etree_item_list\x18\x02 \x03(\x0b\x32\'.OakSave.OakAbilityTreeItemSaveGameData\x12>\n\x11\x61\x62ility_slot_list\x18\x03 \x03(\x0b\x32#.OakSave.OakAbilitySlotSaveGameData\x12G\n\x11\x61ugment_slot_list\x18\x04 \x03(\x0b\x32,.OakSave.OakActionAbilityAugmentSaveGameData\x12]\n\x1a\x61ugment_configuration_list\x18\x05 \x03(\x0b\x32\x39.OakSave.OakActionAbilityAugmentConfigurationSaveGameData\x12\x12\n\ntree_grade\x18\x06 \x01(\x05\"\x8f\x03\n\x1fMissionStatusPlayerSaveGameData\x12\x45\n\x06status\x18\x01 \x01(\x0e\x32\x35.OakSave.MissionStatusPlayerSaveGameData.MissionState\x12\x1e\n\x16has_been_viewed_in_log\x18\x02 \x01(\x08\x12\x1b\n\x13objectives_progress\x18\x03 \x03(\x05\x12\x1a\n\x12mission_class_path\x18\x04 \x01(\t\x12!\n\x19\x61\x63tive_objective_set_path\x18\x05 \x01(\t\x12\x16\n\x0e\x64lc_package_id\x18\x06 \x01(\r\x12\x16\n\x0ekickoff_played\x18\x07 \x01(\x08\x12\x17\n\x0fleague_instance\x18\x08 \x01(\r\"`\n\x0cMissionState\x12\x11\n\rMS_NotStarted\x10\x00\x12\r\n\tMS_Active\x10\x01\x12\x0f\n\x0bMS_Complete\x10\x02\x12\r\n\tMS_Failed\x10\x03\x12\x0e\n\nMS_Unknown\x10\x04\"\x84\x01\n\x1eMissionPlaythroughSaveGameData\x12>\n\x0cmission_list\x18\x01 \x03(\x0b\x32(.OakSave.MissionStatusPlayerSaveGameData\x12\"\n\x1atracked_mission_class_path\x18\x02 \x01(\t\"S\n\x18\x41\x63tiveFastTravelSaveData\x12\"\n\x1a\x61\x63tive_travel_station_name\x18\x01 \x01(\t\x12\x13\n\x0b\x62lacklisted\x18\x02 \x01(\x08\"h\n#PlaythroughActiveFastTravelSaveData\x12\x41\n\x16\x61\x63tive_travel_stations\x18\x01 \x03(\x0b\x32!.OakSave.ActiveFastTravelSaveData\"S\n\x12\x44iscoveredAreaInfo\x12\x1c\n\x14\x64iscovered_area_name\x18\x01 \x01(\t\x12\x1f\n\x17\x64iscovered_playthroughs\x18\x02 \x01(\r\"\x90\x01\n\x13\x44iscoveredLevelInfo\x12\x1d\n\x15\x64iscovered_level_name\x18\x01 \x01(\t\x12\x1f\n\x17\x64iscovered_playthroughs\x18\x03 \x01(\r\x12\x39\n\x14\x64iscovered_area_info\x18\x04 \x03(\x0b\x32\x1b.OakSave.DiscoveredAreaInfo\"H\n\x14\x44iscoveredPlanetInfo\x12\x19\n\x11\x64iscovered_planet\x18\x01 \x01(\t\x12\x15\n\ris_new_planet\x18\x02 \x01(\x08\"P\n\x11\x44iscoverySaveData\x12;\n\x15\x64iscovered_level_info\x18\x01 \x03(\x0b\x32\x1c.OakSave.DiscoveredLevelInfo\"H\n\x1bVehicleUnlockedSaveGameData\x12\x12\n\nasset_path\x18\x01 \x01(\t\x12\x15\n\rjust_unlocked\x18\x02 \x01(\x08\"\x8c\x03\n\x1fOakCARMenuVehicleConfigSaveData\x12\x19\n\x11loadout_save_name\x18\x01 \x01(\t\x12\x17\n\x0f\x62ody_asset_path\x18\x02 \x01(\t\x12\x18\n\x10wheel_asset_path\x18\x03 \x01(\t\x12\x18\n\x10\x61rmor_asset_path\x18\x04 \x01(\t\x12\x1b\n\x13\x63ore_mod_asset_path\x18\x05 \x01(\t\x12 \n\x18gunner_weapon_asset_path\x18\x06 \x01(\t\x12 \n\x18\x64river_weapon_asset_path\x18\x07 \x01(\t\x12\x1b\n\x13ornament_asset_path\x18\x08 \x01(\t\x12!\n\x19material_decal_asset_path\x18\t \x01(\t\x12\x1b\n\x13material_asset_path\x18\n \x01(\t\x12\x15\n\rcolor_index_1\x18\x0b \x01(\x05\x12\x15\n\rcolor_index_2\x18\x0c \x01(\x05\x12\x15\n\rcolor_index_3\x18\r \x01(\x05\"\xbe\x01\n\x1d\x43ustomPlayerColorSaveGameData\x12\x17\n\x0f\x63olor_parameter\x18\x01 \x01(\t\x12$\n\rapplied_color\x18\x02 \x01(\x0b\x32\r.OakSave.Vec3\x12\"\n\x0bsplit_color\x18\x03 \x01(\x0b\x32\r.OakSave.Vec3\x12\x19\n\x11use_default_color\x18\x04 \x01(\x08\x12\x1f\n\x17use_default_split_color\x18\x05 \x01(\x08\"N\n\x18GuardianRankSaveGameData\x12\x15\n\rguardian_rank\x18\x01 \x01(\x05\x12\x1b\n\x13guardian_experience\x18\x02 \x01(\x05\"k\n\'GuardianRankRewardCharacterSaveGameData\x12\x12\n\nnum_tokens\x18\x01 \x01(\x05\x12\x12\n\nis_enabled\x18\x02 \x01(\x08\x12\x18\n\x10reward_data_path\x18\x03 \x01(\t\"S\n%GuardianRankPerkCharacterSaveGameData\x12\x12\n\nis_enabled\x18\x01 \x01(\x08\x12\x16\n\x0eperk_data_path\x18\x02 \x01(\t\"\xec\x02\n!GuardianRankCharacterSaveGameData\x12!\n\x19guardian_available_tokens\x18\x01 \x01(\x05\x12\x15\n\rguardian_rank\x18\x02 \x01(\x05\x12\x1b\n\x13guardian_experience\x18\x03 \x01(\x05\x12\x46\n\x0crank_rewards\x18\x04 \x03(\x0b\x32\x30.OakSave.GuardianRankRewardCharacterSaveGameData\x12\x42\n\nrank_perks\x18\x05 \x03(\x0b\x32..OakSave.GuardianRankPerkCharacterSaveGameData\x12#\n\x1bguardian_reward_random_seed\x18\x06 \x01(\x05\x12\x1f\n\x17new_guardian_experience\x18\x07 \x01(\x03\x12\x1e\n\x16is_rank_system_enabled\x18\x08 \x01(\x08\"X\n\x1e\x43rewQuartersDecorationSaveData\x12\x18\n\x10\x64\x65\x63oration_index\x18\x01 \x01(\x05\x12\x1c\n\x14\x64\x65\x63oration_data_path\x18\x02 \x01(\t\"\x8f\x01\n\x14\x43rewQuartersSaveData\x12!\n\x19preferred_room_assignment\x18\x01 \x01(\x05\x12<\n\x0b\x64\x65\x63orations\x18\x02 \x03(\x0b\x32\'.OakSave.CrewQuartersDecorationSaveData\x12\x16\n\x0eroom_data_path\x18\x03 \x01(\t\"\xac\x01\n\x1f\x43rewQuartersGunRackItemSaveData\x12\x1f\n\x17\x65ncrypted_serial_number\x18\x01 \x01(\x0c\x12\x17\n\x0fslot_asset_path\x18\x02 \x01(\t\x12O\n\x15\x64\x65velopment_save_data\x18\x03 \x01(\x0b\x32\x30.OakSave.InventoryBalanceStateInitializationData\"_\n\x1b\x43rewQuartersGunRackSaveData\x12@\n\x0erack_save_data\x18\x01 \x03(\x0b\x32(.OakSave.CrewQuartersGunRackItemSaveData\"J\n\x13\x45\x63hoLogSaveGameData\x12\x1c\n\x14has_been_seen_in_log\x18\x01 \x01(\x08\x12\x15\n\recho_log_path\x18\x02 \x01(\t\"6\n\tMapIDData\x12\x14\n\x0czone_name_id\x18\x01 \x01(\r\x12\x13\n\x0bmap_name_id\x18\x02 \x01(\r\"w\n\x11GameStateSaveData\x12\x30\n\x14last_traveled_map_id\x18\x01 \x01(\x0b\x32\x12.OakSave.MapIDData\x12\x14\n\x0cmayhem_level\x18\x02 \x01(\x05\x12\x1a\n\x12mayhem_random_seed\x18\x03 \x01(\x05\">\n!ChallengeCategoryProgressSaveData\x12\x19\n\x11\x63\x61tegory_progress\x18\x01 \x01(\x0c\"\\\n%OakPlayerCharacterAugmentSaveGameData\x12\x17\n\x0fslot_asset_path\x18\x01 \x01(\t\x12\x1a\n\x12\x61ugment_asset_path\x18\x02 \x01(\t\"o\n\"OakPlayerCharacterSlotSaveGameData\x12I\n\x11\x61ugment_slot_list\x18\x01 \x03(\x0b\x32..OakSave.OakPlayerCharacterAugmentSaveGameData\"\xbf\x02\n\x16UITrackingSaveGameData\x12\"\n\x1ahas_seen_skill_menu_unlock\x18\x01 \x01(\x08\x12*\n\"has_seen_guardian_rank_menu_unlock\x18\x02 \x01(\x08\x12#\n\x1bhas_seen_echo_boot_ammo_bar\x18\x03 \x01(\x08\x12%\n\x1dhas_seen_echo_boot_shield_bar\x18\x04 \x01(\x08\x12#\n\x1bhas_seen_echo_boot_grenades\x18\x05 \x01(\x08\x12$\n\x1chighest_thvm_breadcrumb_seen\x18\x06 \x01(\x05\x12#\n\x1binventory_slot_unlocks_seen\x18\x07 \x03(\t\x12\x19\n\x11saved_spin_offset\x18\x08 \x01(\x05\"V\n\x0fPlanetCycleInfo\x12\x13\n\x0bplanet_name\x18\x01 \x01(\t\x12\x14\n\x0c\x63ycle_length\x18\x02 \x01(\x02\x12\x18\n\x10last_cached_time\x18\x03 \x01(\x02\"b\n\x15TimeOfDaySaveGameData\x12\x33\n\x11planet_cycle_info\x18\x01 \x03(\x0b\x32\x18.OakSave.PlanetCycleInfo\x12\x14\n\x0cplanet_cycle\x18\x02 \x01(\t\"R\n#LevelPersistence_Actor_SaveGameData\x12\x12\n\nactor_name\x18\x01 \x01(\t\x12\x17\n\x0ftimer_remaining\x18\x02 \x01(\x05\"}\n#LevelPersistence_Level_SaveGameData\x12\x12\n\nlevel_name\x18\x01 \x01(\t\x12\x42\n\x0csaved_actors\x18\x02 \x03(\x0b\x32,.OakSave.LevelPersistence_Actor_SaveGameData\"\xba\x01\n\x1bGbxZoneMapFODSavedLevelData\x12\x12\n\nlevel_name\x18\x01 \x01(\t\x12\x18\n\x10\x66od_texture_size\x18\x02 \x01(\r\x12\x12\n\nnum_chunks\x18\x03 \x01(\r\x12\x1c\n\x14\x64iscovery_percentage\x18\x04 \x01(\x02\x12\x12\n\ndata_state\x18\x05 \x01(\r\x12\x15\n\rdata_revision\x18\x06 \x01(\r\x12\x10\n\x08\x66od_data\x18\x07 \x01(\x0c\"U\n\x19GbxZoneMapFODSaveGameData\x12\x38\n\nlevel_data\x18\x01 \x03(\x0b\x32$.OakSave.GbxZoneMapFODSavedLevelData\"\xff\x06\n\x13OakProfileCloudData\x12\x39\n\x12profile_stats_data\x18\x01 \x03(\x0b\x32\x1d.OakSave.GameStatSaveGameData\x12\x1b\n\x13\x62\x61nk_inventory_list\x18\x02 \x03(\x0c\x12 \n\x18lost_loot_inventory_list\x18\x03 \x03(\x0c\x12,\n\x0enpc_mail_items\x18\x04 \x03(\x0b\x32\x14.OakSave.OakMailItem\x12\x35\n\x10profile_sdu_list\x18\x05 \x03(\x0b\x32\x1b.OakSave.OakSDUSaveGameData\x12\x46\n\x17unlocked_customizations\x18\x06 \x03(\x0b\x32%.OakSave.OakCustomizationSaveGameData\x12Z\n&unlocked_inventory_customization_parts\x18\x07 \x03(\x0b\x32*.OakSave.OakInventoryCustomizationPartInfo\x12\x1b\n\x13guardian_experience\x18\x08 \x01(\x03\x12[\n\"unlocked_crew_quarters_decorations\x18\t \x03(\x0b\x32/.OakSave.CrewQuartersDecorationItemSaveGameData\x12O\n\x1cunlocked_crew_quarters_rooms\x18\n \x03(\x0b\x32).OakSave.CrewQuartersRoomItemSaveGameData\x12\x36\n\x0e\x63hallenge_data\x18\x0b \x03(\x0b\x32\x1e.OakSave.ChallengeSaveGameData\x12\x12\n\nmail_guids\x18\x0c \x03(\t\x12&\n\x1e\x43itizenScienceLevelProgression\x18\r \x03(\x05\x12#\n\x1b\x43itizenScienceCSBucksAmount\x18\x0e \x01(\x05\x12\x32\n\nvault_card\x18\x0f \x01(\x0b\x32\x1e.OakSave.VaultCardSaveGameData\x12(\n bCitizenScienceHasSeenIntroVideo\x18\x19 \x01(\x08\x12#\n\x1b\x62\x43itizenScienceTutorialDone\x18\x1a \x01(\x08\"\xf7\x1a\n\tCharacter\x12\x14\n\x0csave_game_id\x18\x01 \x01(\r\x12\x1b\n\x13last_save_timestamp\x18\x02 \x01(\x03\x12\x1b\n\x13time_played_seconds\x18\x03 \x01(\r\x12;\n\x11player_class_data\x18\x04 \x01(\x0b\x32 .OakSave.PlayerClassSaveGameData\x12\x39\n\x0eresource_pools\x18\x05 \x03(\x0b\x32!.OakSave.ResourcePoolSavegameData\x12\x32\n\rsaved_regions\x18\x06 \x03(\x0b\x32\x1b.OakSave.RegionSaveGameData\x12\x19\n\x11\x65xperience_points\x18\x07 \x01(\x05\x12\x36\n\x0fgame_stats_data\x18\x08 \x03(\x0b\x32\x1d.OakSave.GameStatSaveGameData\x12\x43\n\x17inventory_category_list\x18\t \x03(\x0b\x32\".OakSave.InventoryCategorySaveData\x12>\n\x0finventory_items\x18\n \x03(\x0b\x32%.OakSave.OakInventoryItemSaveGameData\x12G\n\x17\x65quipped_inventory_list\x18\x0b \x03(\x0b\x32&.OakSave.EquippedInventorySaveGameData\x12\x1a\n\x12\x61\x63tive_weapon_list\x18\x0c \x03(\x05\x12;\n\x0c\x61\x62ility_data\x18\r \x01(\x0b\x32%.OakSave.OakPlayerAbilitySaveGameData\x12\x1f\n\x17last_play_through_index\x18\x0e \x01(\x05\x12\x1e\n\x16playthroughs_completed\x18\x0f \x01(\x05\x12)\n!show_new_playthrough_notification\x18\x10 \x01(\x08\x12J\n\x19mission_playthroughs_data\x18\x11 \x03(\x0b\x32\'.OakSave.MissionPlaythroughSaveGameData\x12\x1e\n\x16\x61\x63tive_travel_stations\x18\x15 \x03(\t\x12\x32\n\x0e\x64iscovery_data\x18\x16 \x01(\x0b\x32\x1a.OakSave.DiscoverySaveData\x12\"\n\x1alast_active_travel_station\x18\x17 \x01(\t\x12\x44\n\x16vehicles_unlocked_data\x18\x18 \x03(\x0b\x32$.OakSave.VehicleUnlockedSaveGameData\x12\x1e\n\x16vehicle_parts_unlocked\x18\x19 \x03(\t\x12\x42\n\x10vehicle_loadouts\x18\x1a \x03(\x0b\x32(.OakSave.OakCARMenuVehicleConfigSaveData\x12\"\n\x1avehicle_last_loadout_index\x18\x1b \x01(\x05\x12\x36\n\x0e\x63hallenge_data\x18\x1c \x03(\x0b\x32\x1e.OakSave.ChallengeSaveGameData\x12-\n\x08sdu_list\x18\x1d \x03(\x0b\x32\x1b.OakSave.OakSDUSaveGameData\x12\x1f\n\x17selected_customizations\x18\x1e \x03(\t\x12%\n\x1d\x65quipped_emote_customizations\x18\x1f \x03(\x05\x12M\n\x1dselected_color_customizations\x18 \x03(\x0b\x32&.OakSave.CustomPlayerColorSaveGameData\x12\x38\n\rguardian_rank\x18! \x01(\x0b\x32!.OakSave.GuardianRankSaveGameData\x12\x39\n\x12\x63rew_quarters_room\x18\" \x01(\x0b\x32\x1d.OakSave.CrewQuartersSaveData\x12\x44\n\x16\x63rew_quarters_gun_rack\x18# \x01(\x0b\x32$.OakSave.CrewQuartersGunRackSaveData\x12\x38\n\x12unlocked_echo_logs\x18$ \x03(\x0b\x32\x1c.OakSave.EchoLogSaveGameData\x12\x32\n*has_played_special_echo_log_insert_already\x18% \x01(\x08\x12\x43\n\x11nickname_mappings\x18& \x03(\x0b\x32(.OakSave.Character.NicknameMappingsEntry\x12\x30\n\x14last_traveled_map_id\x18\' \x01(\x0b\x32\x12.OakSave.MapIDData\x12V\n\"challenge_category_completion_pcts\x18( \x01(\x0b\x32*.OakSave.ChallengeCategoryProgressSaveData\x12R\n\x1d\x63haracter_slot_save_game_data\x18) \x01(\x0b\x32+.OakSave.OakPlayerCharacterSlotSaveGameData\x12\x43\n\x1aui_tracking_save_game_data\x18* \x01(\x0b\x32\x1f.OakSave.UITrackingSaveGameData\x12 \n\x18preferred_character_name\x18+ \x01(\t\x12\x1c\n\x14name_character_limit\x18, \x01(\x05\x12\x1c\n\x14preferred_group_mode\x18- \x01(\r\x12\x42\n\x1atime_of_day_save_game_data\x18. \x01(\x0b\x32\x1e.OakSave.TimeOfDaySaveGameData\x12L\n\x16level_persistence_data\x18/ \x03(\x0b\x32,.OakSave.LevelPersistence_Level_SaveGameData\x12\x39\n1accumulated_level_persistence_reset_timer_seconds\x18\x30 \x01(\r\x12\x14\n\x0cmayhem_level\x18\x31 \x01(\r\x12K\n\x1fgbx_zone_map_fod_save_game_data\x18\x32 \x01(\x0b\x32\".OakSave.GbxZoneMapFODSaveGameData\x12P\n%active_or_blacklisted_travel_stations\x18\x33 \x03(\x0b\x32!.OakSave.ActiveFastTravelSaveData\x12\x32\n*last_active_travel_station_for_playthrough\x18\x34 \x03(\t\x12H\n$game_state_save_data_for_playthrough\x18\x35 \x03(\x0b\x32\x1a.OakSave.GameStateSaveData\x12Y\n$registered_downloadable_entitlements\x18\x36 \x03(\x0b\x32+.OakSave.RegisteredDownloadableEntitlements\x12\\\n&active_travel_stations_for_playthrough\x18\x37 \x03(\x0b\x32,.OakSave.PlaythroughActiveFastTravelSaveData\x12\x16\n\x0esave_game_guid\x18\x38 \x01(\t\x12P\n\x1cguardian_rank_character_data\x18\x39 \x01(\x0b\x32*.OakSave.GuardianRankCharacterSaveGameData\x12/\n\'optional_objective_reward_fixup_applied\x18: \x01(\x08\x12*\n\"vehicle_part_rewards_fixup_applied\x18; \x01(\x08\x12\x1a\n\x12last_active_league\x18< \x01(\r\x12#\n\x1blast_active_league_instance\x18= \x01(\r\x12^\n active_league_instance_for_event\x18> \x03(\x0b\x32\x34.OakSave.Character.ActiveLeagueInstanceForEventEntry\x12\x38\n0levelled_save_vehicle_part_rewards_fixup_applied\x18? \x01(\x08\x12\x38\n\x12profile_cloud_data\x18@ \x01(\x0b\x32\x1c.OakSave.OakProfileCloudData\x1a\x33\n\x15NicknameMappingsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x1a?\n!ActiveLeagueInstanceForEventEntry\x12\x0b\n\x03key\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\rb\x06proto3') 29 | 30 | _globals = globals() 31 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 32 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'OakSave_pb2', _globals) 33 | if not _descriptor._USE_C_DESCRIPTORS: 34 | DESCRIPTOR._loaded_options = None 35 | _globals['_PLAYERCLASSSAVEGAMEDATA']._serialized_start=43 36 | _globals['_PLAYERCLASSSAVEGAMEDATA']._serialized_end=119 37 | _globals['_RESOURCEPOOLSAVEGAMEDATA']._serialized_start=121 38 | _globals['_RESOURCEPOOLSAVEGAMEDATA']._serialized_end=186 39 | _globals['_REGIONSAVEGAMEDATA']._serialized_start=188 40 | _globals['_REGIONSAVEGAMEDATA']._serialized_end=299 41 | _globals['_INVENTORYBALANCESTATEINITIALIZATIONDATA']._serialized_start=302 42 | _globals['_INVENTORYBALANCESTATEINITIALIZATIONDATA']._serialized_end=550 43 | _globals['_OAKINVENTORYITEMSAVEGAMEDATA']._serialized_start=553 44 | _globals['_OAKINVENTORYITEMSAVEGAMEDATA']._serialized_end=761 45 | _globals['_EQUIPPEDINVENTORYSAVEGAMEDATA']._serialized_start=764 46 | _globals['_EQUIPPEDINVENTORYSAVEGAMEDATA']._serialized_end=893 47 | _globals['_OAKABILITYTREEITEMSAVEGAMEDATA']._serialized_start=895 48 | _globals['_OAKABILITYTREEITEMSAVEGAMEDATA']._serialized_end=1013 49 | _globals['_OAKABILITYSLOTSAVEGAMEDATA']._serialized_start=1015 50 | _globals['_OAKABILITYSLOTSAVEGAMEDATA']._serialized_end=1096 51 | _globals['_OAKACTIONABILITYAUGMENTSAVEGAMEDATA']._serialized_start=1098 52 | _globals['_OAKACTIONABILITYAUGMENTSAVEGAMEDATA']._serialized_end=1223 53 | _globals['_OAKACTIONABILITYAUGMENTCONFIGURATIONSAVEGAMEDATA']._serialized_start=1226 54 | _globals['_OAKACTIONABILITYAUGMENTCONFIGURATIONSAVEGAMEDATA']._serialized_end=1385 55 | _globals['_OAKPLAYERABILITYSAVEGAMEDATA']._serialized_start=1388 56 | _globals['_OAKPLAYERABILITYSAVEGAMEDATA']._serialized_end=1759 57 | _globals['_MISSIONSTATUSPLAYERSAVEGAMEDATA']._serialized_start=1762 58 | _globals['_MISSIONSTATUSPLAYERSAVEGAMEDATA']._serialized_end=2161 59 | _globals['_MISSIONSTATUSPLAYERSAVEGAMEDATA_MISSIONSTATE']._serialized_start=2065 60 | _globals['_MISSIONSTATUSPLAYERSAVEGAMEDATA_MISSIONSTATE']._serialized_end=2161 61 | _globals['_MISSIONPLAYTHROUGHSAVEGAMEDATA']._serialized_start=2164 62 | _globals['_MISSIONPLAYTHROUGHSAVEGAMEDATA']._serialized_end=2296 63 | _globals['_ACTIVEFASTTRAVELSAVEDATA']._serialized_start=2298 64 | _globals['_ACTIVEFASTTRAVELSAVEDATA']._serialized_end=2381 65 | _globals['_PLAYTHROUGHACTIVEFASTTRAVELSAVEDATA']._serialized_start=2383 66 | _globals['_PLAYTHROUGHACTIVEFASTTRAVELSAVEDATA']._serialized_end=2487 67 | _globals['_DISCOVEREDAREAINFO']._serialized_start=2489 68 | _globals['_DISCOVEREDAREAINFO']._serialized_end=2572 69 | _globals['_DISCOVEREDLEVELINFO']._serialized_start=2575 70 | _globals['_DISCOVEREDLEVELINFO']._serialized_end=2719 71 | _globals['_DISCOVEREDPLANETINFO']._serialized_start=2721 72 | _globals['_DISCOVEREDPLANETINFO']._serialized_end=2793 73 | _globals['_DISCOVERYSAVEDATA']._serialized_start=2795 74 | _globals['_DISCOVERYSAVEDATA']._serialized_end=2875 75 | _globals['_VEHICLEUNLOCKEDSAVEGAMEDATA']._serialized_start=2877 76 | _globals['_VEHICLEUNLOCKEDSAVEGAMEDATA']._serialized_end=2949 77 | _globals['_OAKCARMENUVEHICLECONFIGSAVEDATA']._serialized_start=2952 78 | _globals['_OAKCARMENUVEHICLECONFIGSAVEDATA']._serialized_end=3348 79 | _globals['_CUSTOMPLAYERCOLORSAVEGAMEDATA']._serialized_start=3351 80 | _globals['_CUSTOMPLAYERCOLORSAVEGAMEDATA']._serialized_end=3541 81 | _globals['_GUARDIANRANKSAVEGAMEDATA']._serialized_start=3543 82 | _globals['_GUARDIANRANKSAVEGAMEDATA']._serialized_end=3621 83 | _globals['_GUARDIANRANKREWARDCHARACTERSAVEGAMEDATA']._serialized_start=3623 84 | _globals['_GUARDIANRANKREWARDCHARACTERSAVEGAMEDATA']._serialized_end=3730 85 | _globals['_GUARDIANRANKPERKCHARACTERSAVEGAMEDATA']._serialized_start=3732 86 | _globals['_GUARDIANRANKPERKCHARACTERSAVEGAMEDATA']._serialized_end=3815 87 | _globals['_GUARDIANRANKCHARACTERSAVEGAMEDATA']._serialized_start=3818 88 | _globals['_GUARDIANRANKCHARACTERSAVEGAMEDATA']._serialized_end=4182 89 | _globals['_CREWQUARTERSDECORATIONSAVEDATA']._serialized_start=4184 90 | _globals['_CREWQUARTERSDECORATIONSAVEDATA']._serialized_end=4272 91 | _globals['_CREWQUARTERSSAVEDATA']._serialized_start=4275 92 | _globals['_CREWQUARTERSSAVEDATA']._serialized_end=4418 93 | _globals['_CREWQUARTERSGUNRACKITEMSAVEDATA']._serialized_start=4421 94 | _globals['_CREWQUARTERSGUNRACKITEMSAVEDATA']._serialized_end=4593 95 | _globals['_CREWQUARTERSGUNRACKSAVEDATA']._serialized_start=4595 96 | _globals['_CREWQUARTERSGUNRACKSAVEDATA']._serialized_end=4690 97 | _globals['_ECHOLOGSAVEGAMEDATA']._serialized_start=4692 98 | _globals['_ECHOLOGSAVEGAMEDATA']._serialized_end=4766 99 | _globals['_MAPIDDATA']._serialized_start=4768 100 | _globals['_MAPIDDATA']._serialized_end=4822 101 | _globals['_GAMESTATESAVEDATA']._serialized_start=4824 102 | _globals['_GAMESTATESAVEDATA']._serialized_end=4943 103 | _globals['_CHALLENGECATEGORYPROGRESSSAVEDATA']._serialized_start=4945 104 | _globals['_CHALLENGECATEGORYPROGRESSSAVEDATA']._serialized_end=5007 105 | _globals['_OAKPLAYERCHARACTERAUGMENTSAVEGAMEDATA']._serialized_start=5009 106 | _globals['_OAKPLAYERCHARACTERAUGMENTSAVEGAMEDATA']._serialized_end=5101 107 | _globals['_OAKPLAYERCHARACTERSLOTSAVEGAMEDATA']._serialized_start=5103 108 | _globals['_OAKPLAYERCHARACTERSLOTSAVEGAMEDATA']._serialized_end=5214 109 | _globals['_UITRACKINGSAVEGAMEDATA']._serialized_start=5217 110 | _globals['_UITRACKINGSAVEGAMEDATA']._serialized_end=5536 111 | _globals['_PLANETCYCLEINFO']._serialized_start=5538 112 | _globals['_PLANETCYCLEINFO']._serialized_end=5624 113 | _globals['_TIMEOFDAYSAVEGAMEDATA']._serialized_start=5626 114 | _globals['_TIMEOFDAYSAVEGAMEDATA']._serialized_end=5724 115 | _globals['_LEVELPERSISTENCE_ACTOR_SAVEGAMEDATA']._serialized_start=5726 116 | _globals['_LEVELPERSISTENCE_ACTOR_SAVEGAMEDATA']._serialized_end=5808 117 | _globals['_LEVELPERSISTENCE_LEVEL_SAVEGAMEDATA']._serialized_start=5810 118 | _globals['_LEVELPERSISTENCE_LEVEL_SAVEGAMEDATA']._serialized_end=5935 119 | _globals['_GBXZONEMAPFODSAVEDLEVELDATA']._serialized_start=5938 120 | _globals['_GBXZONEMAPFODSAVEDLEVELDATA']._serialized_end=6124 121 | _globals['_GBXZONEMAPFODSAVEGAMEDATA']._serialized_start=6126 122 | _globals['_GBXZONEMAPFODSAVEGAMEDATA']._serialized_end=6211 123 | _globals['_OAKPROFILECLOUDDATA']._serialized_start=6214 124 | _globals['_OAKPROFILECLOUDDATA']._serialized_end=7109 125 | _globals['_CHARACTER']._serialized_start=7112 126 | _globals['_CHARACTER']._serialized_end=10559 127 | _globals['_CHARACTER_NICKNAMEMAPPINGSENTRY']._serialized_start=10443 128 | _globals['_CHARACTER_NICKNAMEMAPPINGSENTRY']._serialized_end=10494 129 | _globals['_CHARACTER_ACTIVELEAGUEINSTANCEFOREVENTENTRY']._serialized_start=10496 130 | _globals['_CHARACTER_ACTIVELEAGUEINSTANCEFOREVENTENTRY']._serialized_end=10559 131 | # @@protoc_insertion_point(module_scope) 132 | -------------------------------------------------------------------------------- /bl3save/OakShared_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # NO CHECKED-IN PROTOBUF GENCODE 4 | # source: OakShared.proto 5 | # Protobuf Python Version: 5.27.2 6 | """Generated protocol buffer code.""" 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import descriptor_pool as _descriptor_pool 9 | from google.protobuf import runtime_version as _runtime_version 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf.internal import builder as _builder 12 | _runtime_version.ValidateProtobufRuntimeVersion( 13 | _runtime_version.Domain.PUBLIC, 14 | 5, 15 | 27, 16 | 2, 17 | '', 18 | 'OakShared.proto' 19 | ) 20 | # @@protoc_insertion_point(imports) 21 | 22 | _sym_db = _symbol_database.Default() 23 | 24 | 25 | 26 | 27 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fOakShared.proto\x12\x07OakSave\"\'\n\x04Vec3\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\"=\n\x14GameStatSaveGameData\x12\x12\n\nstat_value\x18\x01 \x01(\x05\x12\x11\n\tstat_path\x18\x02 \x01(\t\"T\n\x19InventoryCategorySaveData\x12%\n\x1d\x62\x61se_category_definition_hash\x18\x01 \x01(\r\x12\x10\n\x08quantity\x18\x02 \x01(\x05\">\n\x12OakSDUSaveGameData\x12\x11\n\tsdu_level\x18\x01 \x01(\x05\x12\x15\n\rsdu_data_path\x18\x02 \x01(\t\"c\n!RegisteredDownloadableEntitlement\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x10\n\x08\x63onsumed\x18\x02 \x01(\r\x12\x12\n\nregistered\x18\x03 \x01(\x08\x12\x0c\n\x04seen\x18\x04 \x01(\x08\"\xa6\x01\n\"RegisteredDownloadableEntitlements\x12%\n\x1d\x65ntitlement_source_asset_path\x18\x01 \x01(\t\x12\x17\n\x0f\x65ntitlement_ids\x18\x02 \x03(\x03\x12@\n\x0c\x65ntitlements\x18\x03 \x03(\x0b\x32*.OakSave.RegisteredDownloadableEntitlement\"T\n\x19\x43hallengeStatSaveGameData\x12\x1a\n\x12\x63urrent_stat_value\x18\x01 \x01(\x05\x12\x1b\n\x13\x63hallenge_stat_path\x18\x02 \x01(\t\"B\n\x1eOakChallengeRewardSaveGameData\x12 \n\x18\x63hallenge_reward_claimed\x18\x01 \x01(\x08\"\xc3\x02\n\x15\x43hallengeSaveGameData\x12\x17\n\x0f\x63ompleted_count\x18\x01 \x01(\x05\x12\x11\n\tis_active\x18\x02 \x01(\x08\x12\x1b\n\x13\x63urrently_completed\x18\x03 \x01(\x08\x12 \n\x18\x63ompleted_progress_level\x18\x04 \x01(\x05\x12\x18\n\x10progress_counter\x18\x05 \x01(\x05\x12?\n\x13stat_instance_state\x18\x06 \x03(\x0b\x32\".OakSave.ChallengeStatSaveGameData\x12\x1c\n\x14\x63hallenge_class_path\x18\x07 \x01(\t\x12\x46\n\x15\x63hallenge_reward_info\x18\x08 \x03(\x0b\x32\'.OakSave.OakChallengeRewardSaveGameData\"\xeb\x01\n\x0bOakMailItem\x12\x16\n\x0email_item_type\x18\x01 \x01(\r\x12\x1b\n\x13sender_display_name\x18\x02 \x01(\t\x12\x0f\n\x07subject\x18\x03 \x01(\t\x12\x0c\n\x04\x62ody\x18\x04 \x01(\t\x12\x1a\n\x12gear_serial_number\x18\x05 \x01(\t\x12\x11\n\tmail_guid\x18\x06 \x01(\t\x12\x11\n\tdate_sent\x18\x07 \x01(\x03\x12\x17\n\x0f\x65xpiration_date\x18\x08 \x01(\x03\x12\x16\n\x0e\x66rom_player_id\x18\t \x01(\t\x12\x15\n\rhas_been_read\x18\n \x01(\x08\"P\n\x1cOakCustomizationSaveGameData\x12\x0e\n\x06is_new\x18\x01 \x01(\x08\x12 \n\x18\x63ustomization_asset_path\x18\x02 \x01(\t\"T\n!OakInventoryCustomizationPartInfo\x12\x1f\n\x17\x63ustomization_part_hash\x18\x01 \x01(\r\x12\x0e\n\x06is_new\x18\x02 \x01(\x08\"\\\n&CrewQuartersDecorationItemSaveGameData\x12\x0e\n\x06is_new\x18\x01 \x01(\x08\x12\"\n\x1a\x64\x65\x63oration_item_asset_path\x18\x02 \x01(\t\"P\n CrewQuartersRoomItemSaveGameData\x12\x0e\n\x06is_new\x18\x01 \x01(\x08\x12\x1c\n\x14room_item_asset_path\x18\x02 \x01(\t\"\xfe\x01\n\x15VaultCardSaveGameData\x12!\n\x19last_active_vault_card_id\x18\x02 \x01(\r\x12\x18\n\x10\x63urrent_day_seed\x18\x03 \x01(\x05\x12\x19\n\x11\x63urrent_week_seed\x18\x04 \x01(\x05\x12K\n\x1evault_card_previous_challenges\x18\x05 \x03(\x0b\x32#.OakSave.VaultCardPreviousChallenge\x12@\n\x1avault_card_claimed_rewards\x18\x06 \x03(\x0b\x32\x1c.OakSave.VaultCardRewardList\":\n\x0fVaultCardReward\x12\x14\n\x0c\x63olumn_index\x18\x01 \x01(\x05\x12\x11\n\trow_index\x18\x02 \x01(\x05\"C\n\x13VaultCardGearReward\x12\x12\n\ngear_index\x18\x01 \x01(\x05\x12\x18\n\x10repurchase_count\x18\x02 \x01(\r\"\xcb\x02\n\x13VaultCardRewardList\x12\x15\n\rvault_card_id\x18\x01 \x01(\r\x12\x1d\n\x15vault_card_experience\x18\x02 \x01(\x03\x12\x36\n\x14unlocked_reward_list\x18\x04 \x03(\x0b\x32\x18.OakSave.VaultCardReward\x12\x36\n\x14redeemed_reward_list\x18\x05 \x03(\x0b\x32\x18.OakSave.VaultCardReward\x12\x19\n\x11vault_card_chests\x18\x07 \x01(\x05\x12 \n\x18vault_card_chests_opened\x18\x08 \x01(\r\x12\x1d\n\x15vault_card_keys_spent\x18\t \x01(\r\x12\x32\n\x0cgear_rewards\x18\n \x03(\x0b\x32\x1c.OakSave.VaultCardGearReward\"\\\n\x1aVaultCardPreviousChallenge\x12\x1f\n\x17previous_challenge_seed\x18\x01 \x01(\x05\x12\x1d\n\x15previous_challenge_id\x18\x02 \x01(\rb\x06proto3') 28 | 29 | _globals = globals() 30 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 31 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'OakShared_pb2', _globals) 32 | if not _descriptor._USE_C_DESCRIPTORS: 33 | DESCRIPTOR._loaded_options = None 34 | _globals['_VEC3']._serialized_start=28 35 | _globals['_VEC3']._serialized_end=67 36 | _globals['_GAMESTATSAVEGAMEDATA']._serialized_start=69 37 | _globals['_GAMESTATSAVEGAMEDATA']._serialized_end=130 38 | _globals['_INVENTORYCATEGORYSAVEDATA']._serialized_start=132 39 | _globals['_INVENTORYCATEGORYSAVEDATA']._serialized_end=216 40 | _globals['_OAKSDUSAVEGAMEDATA']._serialized_start=218 41 | _globals['_OAKSDUSAVEGAMEDATA']._serialized_end=280 42 | _globals['_REGISTEREDDOWNLOADABLEENTITLEMENT']._serialized_start=282 43 | _globals['_REGISTEREDDOWNLOADABLEENTITLEMENT']._serialized_end=381 44 | _globals['_REGISTEREDDOWNLOADABLEENTITLEMENTS']._serialized_start=384 45 | _globals['_REGISTEREDDOWNLOADABLEENTITLEMENTS']._serialized_end=550 46 | _globals['_CHALLENGESTATSAVEGAMEDATA']._serialized_start=552 47 | _globals['_CHALLENGESTATSAVEGAMEDATA']._serialized_end=636 48 | _globals['_OAKCHALLENGEREWARDSAVEGAMEDATA']._serialized_start=638 49 | _globals['_OAKCHALLENGEREWARDSAVEGAMEDATA']._serialized_end=704 50 | _globals['_CHALLENGESAVEGAMEDATA']._serialized_start=707 51 | _globals['_CHALLENGESAVEGAMEDATA']._serialized_end=1030 52 | _globals['_OAKMAILITEM']._serialized_start=1033 53 | _globals['_OAKMAILITEM']._serialized_end=1268 54 | _globals['_OAKCUSTOMIZATIONSAVEGAMEDATA']._serialized_start=1270 55 | _globals['_OAKCUSTOMIZATIONSAVEGAMEDATA']._serialized_end=1350 56 | _globals['_OAKINVENTORYCUSTOMIZATIONPARTINFO']._serialized_start=1352 57 | _globals['_OAKINVENTORYCUSTOMIZATIONPARTINFO']._serialized_end=1436 58 | _globals['_CREWQUARTERSDECORATIONITEMSAVEGAMEDATA']._serialized_start=1438 59 | _globals['_CREWQUARTERSDECORATIONITEMSAVEGAMEDATA']._serialized_end=1530 60 | _globals['_CREWQUARTERSROOMITEMSAVEGAMEDATA']._serialized_start=1532 61 | _globals['_CREWQUARTERSROOMITEMSAVEGAMEDATA']._serialized_end=1612 62 | _globals['_VAULTCARDSAVEGAMEDATA']._serialized_start=1615 63 | _globals['_VAULTCARDSAVEGAMEDATA']._serialized_end=1869 64 | _globals['_VAULTCARDREWARD']._serialized_start=1871 65 | _globals['_VAULTCARDREWARD']._serialized_end=1929 66 | _globals['_VAULTCARDGEARREWARD']._serialized_start=1931 67 | _globals['_VAULTCARDGEARREWARD']._serialized_end=1998 68 | _globals['_VAULTCARDREWARDLIST']._serialized_start=2001 69 | _globals['_VAULTCARDREWARDLIST']._serialized_end=2332 70 | _globals['_VAULTCARDPREVIOUSCHALLENGE']._serialized_start=2334 71 | _globals['_VAULTCARDPREVIOUSCHALLENGE']._serialized_end=2426 72 | # @@protoc_insertion_point(module_scope) 73 | -------------------------------------------------------------------------------- /bl3save/cli_archive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2022 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import os 25 | import sys 26 | import argparse 27 | import bl3save 28 | from bl3save.bl3save import BL3Save 29 | 30 | def main(): 31 | 32 | # Set up args 33 | parser = argparse.ArgumentParser( 34 | description='Process Mod-Testing Borderlands 3 Archive Savegames v{}'.format(bl3save.__version__), 35 | ) 36 | 37 | parser.add_argument('-V', '--version', 38 | action='version', 39 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 40 | ) 41 | 42 | group = parser.add_mutually_exclusive_group() 43 | 44 | group.add_argument('-f', '--filename', 45 | type=str, 46 | help='Specific filename to process') 47 | 48 | group.add_argument('-d', '--directory', 49 | type=str, 50 | help='Directory to process (defaults to "step")') 51 | 52 | parser.add_argument('-i', '--info', 53 | type=str, 54 | help='HTML File to write output summary to') 55 | 56 | parser.add_argument('-r', '--reverse', 57 | action='store_true', 58 | help='Reverse the order of CSS in HTML rows') 59 | 60 | parser.add_argument('-o', '--output', 61 | type=str, 62 | required=True, 63 | help='Output filename/directory to use') 64 | 65 | parser.add_argument('-c', '--clobber', 66 | action='store_true', 67 | help='Clobber (overwrite) files without asking') 68 | 69 | # Parse args 70 | args = parser.parse_args() 71 | if not args.filename and not args.directory: 72 | args.directory = 'step' 73 | 74 | # Construct a list of filenames 75 | targets = [] 76 | if args.directory: 77 | for filename in sorted(os.listdir(args.directory)): 78 | if '.sav' in filename: 79 | targets.append(os.path.join(args.directory, filename)) 80 | else: 81 | targets.append(args.filename) 82 | 83 | # If we're a directory, make sure it exists 84 | if not os.path.exists(args.output): 85 | os.mkdir(args.output) 86 | 87 | # If we've been given an info file, check to see if it exists 88 | if args.info and not args.clobber and os.path.exists(args.info): 89 | sys.stdout.write('WARNING: {} already exists. Overwrite [y/N/a/q]? '.format(args.info)) 90 | sys.stdout.flush() 91 | response = sys.stdin.readline().strip().lower() 92 | if response == 'y': 93 | pass 94 | elif response == 'n': 95 | args.info = None 96 | elif response == 'a': 97 | args.clobber = True 98 | elif response == 'q': 99 | sys.exit(1) 100 | else: 101 | # Default to No 102 | args.info = None 103 | 104 | # Open the info file, if we have one. 105 | if args.info: 106 | idf = open(args.info, 'w') 107 | 108 | # Now loop through and process 109 | files_written = 0 110 | if args.reverse: 111 | row_offset = 1 112 | else: 113 | row_offset = 0 114 | for filename in targets: 115 | 116 | # Figure out an output filename 117 | if args.filename: 118 | base_filename = args.filename 119 | output_filename = args.output 120 | else: 121 | base_filename = filename.split('/')[-1] 122 | output_filename = os.path.join(args.output, base_filename) 123 | 124 | # See if the path already exists 125 | if os.path.exists(output_filename) and not args.clobber: 126 | sys.stdout.write('WARNING: {} already exists. Overwrite [y/N/a/q]? '.format(output_filename)) 127 | sys.stdout.flush() 128 | response = sys.stdin.readline().strip().lower() 129 | if response == 'y': 130 | pass 131 | elif response == 'n': 132 | continue 133 | elif response == 'a': 134 | args.clobber = True 135 | elif response == 'q': 136 | break 137 | else: 138 | # Default to No 139 | response = 'n' 140 | 141 | # Load! 142 | print('Processing: {}'.format(filename)) 143 | save = BL3Save(filename) 144 | 145 | # Write to our info file, if we have it 146 | if args.info: 147 | 148 | # Write out the row 149 | print(''.format((files_written + row_offset) % 2), file=idf) 150 | print('{}'.format(base_filename, base_filename), file=idf) 151 | print('{}'.format(save.get_pt_last_map(0, True)), file=idf) 152 | missions = save.get_pt_active_mission_list(0, True) 153 | if len(missions) == 0: 154 | print(' ', file=idf) 155 | else: 156 | print('', file=idf) 157 | print('
    ', file=idf) 158 | for mission in sorted(missions): 159 | print('
  • {}
  • '.format(mission), file=idf) 160 | print('
', file=idf) 161 | print('', file=idf) 162 | print('', file=idf) 163 | 164 | # May as well force the name, while we're at it 165 | save.set_char_name("BL3 Savegame Archive") 166 | 167 | # Max XP 168 | save.set_level(bl3save.max_level) 169 | 170 | # Max SDUs 171 | save.set_max_sdus() 172 | 173 | # Max Ammo 174 | save.set_max_ammo() 175 | 176 | # Unlock all inventory slots 177 | save.unlock_slots() 178 | 179 | # Unlock PT2 180 | # (In the original runthrough which I've already checked in, I'd accidentally set 181 | # this to 2. Whoops! Doesn't seem to matter, so whatever.) 182 | save.set_playthroughs_completed(1) 183 | 184 | # Remove our bogus third playthrough, if we're processing a file which happens 185 | # to still have that (thanks to our faux pas, above) 186 | if save.get_max_playthrough_with_data() > 1: 187 | save.clear_playthrough_data(2) 188 | 189 | # Copy mission/FT/location/mayhem status from PT1 to PT2 190 | save.copy_playthrough_data() 191 | 192 | # Inventory - force our testing gear 193 | # Gear data just taken from my modtest char. Level 57 Mayhem 10, though 194 | # they'll get upgraded if needed, below. 195 | craders = 'BL3(AwAAAADHQ4C6yJOBkHsckEekyWhISinQpbNyysgdQgAAAAAAADIgAA==)' 196 | transformer = 'BL3(AwAAAACSdIC2t9hAkysShLxMKkMEAA==)' 197 | save.overwrite_item_in_slot_encoded(bl3save.WEAPON1, craders) 198 | save.overwrite_item_in_slot_encoded(bl3save.SHIELD, transformer) 199 | 200 | # Bring testing gear up to our max level, while we're at it. 201 | for item in save.get_items(): 202 | if item.level != bl3save.max_level: 203 | item.level = bl3save.max_level 204 | if item.mayhem_level != bl3save.mayhem_max: 205 | item.mayhem_level = bl3save.mayhem_max 206 | 207 | # Wipe guardian rank 208 | save.zero_guardian_rank() 209 | 210 | # Write out 211 | save.save_to(output_filename) 212 | files_written += 1 213 | 214 | if args.filename: 215 | if files_written == 1: 216 | print('Done! Wrote to {}'.format(args.output)) 217 | else: 218 | if files_written == 1: 219 | plural = '' 220 | else: 221 | plural = 's' 222 | print('Done! Wrote {} file{} to {}'.format(files_written, plural, args.output)) 223 | 224 | if args.info: 225 | print('Wrote HTML summary to {}'.format(args.info)) 226 | idf.close() 227 | 228 | if __name__ == '__main__': 229 | main() 230 | 231 | -------------------------------------------------------------------------------- /bl3save/cli_common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import csv 25 | import argparse 26 | 27 | class DictAction(argparse.Action): 28 | """ 29 | Custom argparse action to put list-like arguments into 30 | a dict (where the value will be True) rather than a list. 31 | This is probably implemented fairly shoddily. 32 | """ 33 | def __init__(self, option_strings, dest, nargs=None, **kwargs): 34 | """ 35 | Constructor, taken right from https://docs.python.org/2.7/library/argparse.html#action 36 | """ 37 | if nargs is not None: 38 | raise ValueError('nargs is not allowed') 39 | super(DictAction, self).__init__(option_strings, dest, **kwargs) 40 | 41 | def __call__(self, parser, namespace, values, option_string=None): 42 | """ 43 | Actually setting a value. Forces the attr into a dict if it isn't already. 44 | """ 45 | arg_value = getattr(namespace, self.dest) 46 | if not isinstance(arg_value, dict): 47 | arg_value = {} 48 | arg_value[values] = True 49 | setattr(namespace, self.dest, arg_value) 50 | 51 | def export_items(items, export_file, quiet=False): 52 | """ 53 | Exports the given `items` to the given text `export_file`. If `quiet` is 54 | `False`, only errors will be printed. 55 | """ 56 | with open(export_file, 'w') as df: 57 | for item in items: 58 | if item.eng_name: 59 | print('# {} ({})'.format(item.eng_name, item.get_level_eng()), file=df) 60 | else: 61 | print('# unknown item', file=df) 62 | print(item.get_serial_base64(), file=df) 63 | print('', file=df) 64 | if not quiet: 65 | print('Wrote {} items (in base64 format) to {}'.format(len(items), export_file)) 66 | 67 | def export_items_csv(items, export_file, quiet=False): 68 | """ 69 | Exports the given `items` to the given CSV `export_file`. The first column 70 | will be the English item name, and the second will be the code. If `quiet` is 71 | `False`, only errors will be printed. 72 | """ 73 | with open(export_file, 'w') as df: 74 | writer = csv.writer(df) 75 | for item in items: 76 | if item.eng_name: 77 | name_label = '{} ({})'.format(item.eng_name, item.get_level_eng()) 78 | else: 79 | name_label = 'unknown item' 80 | writer.writerow([ 81 | name_label, 82 | item.get_serial_base64(), 83 | ]) 84 | if not quiet: 85 | print('Wrote {} items (in base64 format) to CSV file {}'.format(len(items), export_file)) 86 | 87 | def import_items(import_file, item_create_func, item_add_func, file_csv=False, allow_fabricator=False, quiet=False): 88 | """ 89 | Imports items from `import_file`. `item_create_func` should point to 90 | a function used to create the item appropriately, and `item_add_func` 91 | should point to a function used to actually add the item into the 92 | appropriate container. If `file_csv` is `True`, we will process the file 93 | as if it's a CSV, otherwise we'll process as if it's a "regular" 94 | text file. If `allow_fabricator` is `False` (the default), 95 | this routine will refuse to import Fabricators, or any item which 96 | can't be decoded (in case it's a Fabricator). If `quiet` is `True`, 97 | only error/warning output will be shown. 98 | """ 99 | if not quiet: 100 | print(' - Importing items from {}'.format(import_file)) 101 | added_count = 0 102 | 103 | # Process the file to find serials 104 | looks_like_csv = False 105 | serial_list = [] 106 | if file_csv: 107 | # For CSV files, we'll look for serial numbers in literally any cell 108 | # of the CSV 109 | with open(import_file) as df: 110 | reader = csv.reader(df) 111 | for row in reader: 112 | for cell in row: 113 | cell = cell.strip() 114 | if cell.lower().startswith('bl3(') and cell.endswith(')'): 115 | serial_list.append(cell) 116 | else: 117 | # For text files, we need the entire line to *just* be a valid serial. 118 | with open(import_file) as df: 119 | for line in df: 120 | itemline = line.strip() 121 | if itemline.lower().startswith('bl3(') and itemline.endswith(')'): 122 | serial_list.append(itemline) 123 | # Also, check to see if we might be a CSV after all, for reporting 124 | # purposes. 125 | if len(serial_list) == 0 and not looks_like_csv: 126 | if ',bl3(' in itemline.lower(): 127 | looks_like_csv = True 128 | 129 | # If we didn't add any items, and the file looked like it might've been a CSV 130 | # (while being processed as a text file), report that to the user, just in case. 131 | if not file_csv and looks_like_csv: 132 | print(' - NOTICE: File looked like a CSV file, try adding --csv to the arguments') 133 | 134 | # Now loop through the serials and see if we should add them 135 | for serial in serial_list: 136 | new_item = item_create_func(serial) 137 | if not allow_fabricator: 138 | # Report these regardless of `quiet` 139 | if not new_item.eng_name: 140 | print(' - NOTICE: Skipping unknown item import because --allow-fabricator is not set') 141 | continue 142 | if new_item.balance_short.lower() == 'balance_eridian_fabricator': 143 | print(' - NOTICE: Skipping Fabricator import because --allow-fabricator is not set') 144 | continue 145 | item_add_func(new_item) 146 | if not quiet: 147 | if new_item.eng_name: 148 | print(' + {} ({})'.format(new_item.eng_name, new_item.get_level_eng())) 149 | else: 150 | print(' + unknown item') 151 | added_count += 1 152 | if not quiet: 153 | print(' - Added Item Count: {}'.format(added_count)) 154 | 155 | def update_item_levels(items, to_level, quiet=False): 156 | """ 157 | Given a list of `items`, update their base level to `level`. If `quiet` 158 | is `True`, only errors will be printed. 159 | """ 160 | num_items = len(items) 161 | if not quiet: 162 | if num_items == 1: 163 | plural = '' 164 | else: 165 | plural = 's' 166 | print(' - Updating {} item{} to level {}'.format( 167 | num_items, 168 | plural, 169 | to_level, 170 | )) 171 | actually_updated = 0 172 | for item in items: 173 | if item.level != to_level: 174 | item.level = to_level 175 | actually_updated += 1 176 | if not quiet: 177 | remaining = num_items - actually_updated 178 | if actually_updated == 1: 179 | updated_verb = 'was' 180 | else: 181 | updated_verb = 'were' 182 | if remaining > 0: 183 | if remaining == 1: 184 | remaining_verb = 'was' 185 | else: 186 | remaining_verb = 'were' 187 | remaining_txt = ' ({} {} already at that level)'.format(remaining, remaining_verb) 188 | else: 189 | remaining_txt = '' 190 | print(' - {} {} updated{}'.format( 191 | actually_updated, 192 | updated_verb, 193 | remaining_txt, 194 | )) 195 | 196 | def update_item_mayhem_levels(items, to_level, quiet=False): 197 | """ 198 | Given a list of `items`, update their mayhem level to `level`. If 199 | `quiet` is `True`, only errors will be printed. 200 | """ 201 | num_items = len(items) 202 | if not quiet: 203 | if num_items == 1: 204 | plural = '' 205 | else: 206 | plural = 's' 207 | print(' - Updating {} item{} to mayhem level {}'.format( 208 | num_items, 209 | plural, 210 | to_level, 211 | )) 212 | actually_updated = 0 213 | not_possible = 0 214 | for item in items: 215 | if item.mayhem_level is None or not item.can_have_mayhem(): 216 | not_possible += 1 217 | elif item.mayhem_level != to_level: 218 | item.mayhem_level = to_level 219 | actually_updated += 1 220 | if not quiet: 221 | remaining = num_items - actually_updated - not_possible 222 | if actually_updated == 1: 223 | updated_verb = 'was' 224 | else: 225 | updated_verb = 'were' 226 | if remaining > 0: 227 | if remaining == 1: 228 | remaining_verb = 'was' 229 | else: 230 | remaining_verb = 'were' 231 | remaining_txt = ' ({} {} already at that level)'.format(remaining, remaining_verb) 232 | else: 233 | remaining_txt = '' 234 | if not_possible > 0: 235 | if not_possible == 1: 236 | not_possible_verb = 'was' 237 | else: 238 | not_possible_verb = 'were' 239 | not_possible_txt = ' ({} {} unable to be levelled)'.format(not_possible, not_possible_verb) 240 | else: 241 | not_possible_txt = '' 242 | print(' - {} {} updated{}{}'.format( 243 | actually_updated, 244 | updated_verb, 245 | remaining_txt, 246 | not_possible_txt 247 | )) 248 | 249 | -------------------------------------------------------------------------------- /bl3save/cli_copy_pt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import os 25 | import sys 26 | import argparse 27 | import bl3save 28 | from bl3save.bl3save import BL3Save 29 | 30 | def main(): 31 | 32 | # Set up args 33 | parser = argparse.ArgumentParser( 34 | description='Copy BL3 Playthrough Data v{}'.format(bl3save.__version__), 35 | ) 36 | 37 | parser.add_argument('-V', '--version', 38 | action='version', 39 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 40 | ) 41 | 42 | parser.add_argument('-f', '--from', 43 | dest='filename_from', 44 | type=str, 45 | required=True, 46 | help='Filename to copy playthrough data from') 47 | 48 | parser.add_argument('-t', '--to', 49 | dest='filename_to', 50 | type=str, 51 | required=True, 52 | help='Filename to copy playthrough data to') 53 | 54 | parser.add_argument('-p', '--playthrough', 55 | type=int, 56 | default=0, 57 | help='Playthrough to copy (defaults to all found playthroughs)') 58 | 59 | parser.add_argument('-c', '--clobber', 60 | action='store_true', 61 | help='Clobber (overwrite) files without asking') 62 | 63 | # Parse args 64 | args = parser.parse_args() 65 | 66 | # Make sure that files exist 67 | if not os.path.exists(args.filename_from): 68 | raise Exception('From filename {} does not exist'.format(args.filename_from)) 69 | if not os.path.exists(args.filename_to): 70 | raise Exception('From filename {} does not exist'.format(args.filename_to)) 71 | if args.filename_from == args.filename_to: 72 | raise argparse.ArgumentTypeError('To and From filenames cannot be the same') 73 | 74 | # Load the from file and do a quick sanity check 75 | save_from = BL3Save(args.filename_from) 76 | total_from_playthroughs = save_from.get_max_playthrough_with_data() + 1 77 | if args.playthrough > 0 and total_from_playthroughs < args.playthrough: 78 | raise Exception('{} does not have Playthrough {} data'.format(args.filename_from, args.playthrough)) 79 | 80 | # Get a list of playthroughs that we'll process 81 | if args.playthrough == 0: 82 | playthroughs = list(range(total_from_playthroughs)) 83 | else: 84 | playthroughs = [args.playthrough-1] 85 | 86 | # Make sure that we can load our "to" file as well, and do a quick sanity check. 87 | # Given that there's only NVHM/TVHM at the moment, this should never actually 88 | # trigger, but I'd accidentally unlocked a third playthrough on my savegame 89 | # archives, so I was able to test it out regardless, in the event that BL3 ever 90 | # gets a third playthrough. 91 | save_to = BL3Save(args.filename_to) 92 | if args.playthrough > 0: 93 | total_to_playthroughs = save_to.get_max_playthrough_with_data() + 1 94 | if total_to_playthroughs == 1: 95 | plural = '' 96 | else: 97 | plural = 's' 98 | if total_to_playthroughs < args.playthrough-1: 99 | raise Exception('Cannot copy playthrough {} data to {}; only has {} playthrough{} currently'.format( 100 | args.playthrough, 101 | args.filename_to, 102 | total_to_playthroughs, 103 | plural, 104 | )) 105 | 106 | # If we've been given an info file, check to see if it exists 107 | if not args.clobber: 108 | if len(playthroughs) == 1: 109 | plural = '' 110 | else: 111 | plural = 's' 112 | 113 | print('WARNING: Playthrough{} {} from {} will be copied into {}'.format( 114 | plural, 115 | '+'.join([str(p+1) for p in playthroughs]), 116 | args.filename_from, 117 | args.filename_to, 118 | )) 119 | sys.stdout.write('Continue [y/N]? ') 120 | sys.stdout.flush() 121 | response = sys.stdin.readline().strip().lower() 122 | if response == 'y': 123 | pass 124 | else: 125 | print('') 126 | print('Aborting!') 127 | print('') 128 | sys.exit(1) 129 | 130 | # If we get here, we're good to go 131 | for pt in playthroughs: 132 | save_to.copy_playthrough_data(from_obj=save_from, from_pt=pt, to_pt=pt) 133 | 134 | # Update our Completed Playthroughs if we need to, so that the copied 135 | # playthroughs are actually active 136 | required_completion = max(playthroughs) 137 | if save_to.get_playthroughs_completed() < required_completion: 138 | save_to.set_playthroughs_completed(required_completion) 139 | 140 | # Write back out to the file 141 | save_to.save_to(args.filename_to) 142 | 143 | # Report! 144 | print('') 145 | print('Done!') 146 | print('') 147 | 148 | if __name__ == '__main__': 149 | main() 150 | 151 | -------------------------------------------------------------------------------- /bl3save/cli_import_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import os 25 | import sys 26 | import argparse 27 | import bl3save 28 | from bl3save.bl3save import BL3Save 29 | 30 | def main(): 31 | 32 | # Set up args 33 | parser = argparse.ArgumentParser( 34 | description='Import BL3 Savegame JSON v{}'.format(bl3save.__version__), 35 | ) 36 | 37 | parser.add_argument('-V', '--version', 38 | action='version', 39 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 40 | ) 41 | 42 | parser.add_argument('-j', '--json', 43 | type=str, 44 | required=True, 45 | help='Filename containing JSON to import') 46 | 47 | parser.add_argument('-t', '--to-filename', 48 | dest='filename_to', 49 | type=str, 50 | required=True, 51 | help='Filename to import JSON into') 52 | 53 | parser.add_argument('-c', '--clobber', 54 | action='store_true', 55 | help='Clobber (overwrite) files without asking') 56 | 57 | # Parse args 58 | args = parser.parse_args() 59 | 60 | # Make sure that files exist 61 | if not os.path.exists(args.filename_to): 62 | raise Exception('Filename {} does not exist'.format(args.filename_to)) 63 | if not os.path.exists(args.json): 64 | raise Exception('Filename {} does not exist'.format(args.json)) 65 | 66 | # Load the savegame file 67 | save_file = BL3Save(args.filename_to) 68 | 69 | # Load the JSON file and import (so we know it's valid before 70 | # we ask for confirmation) 71 | with open(args.json, 'rt') as df: 72 | save_file.import_json(df.read()) 73 | 74 | # Ask for confirmation 75 | if not args.clobber: 76 | sys.stdout.write('Really import JSON from {} into {} [y/N]? '.format( 77 | args.json, 78 | args.filename_to, 79 | )) 80 | sys.stdout.flush() 81 | response = sys.stdin.readline().strip().lower() 82 | if response == 'y': 83 | pass 84 | else: 85 | print('') 86 | print('Aborting!') 87 | print('') 88 | sys.exit(1) 89 | 90 | # ... and save. 91 | save_file.save_to(args.filename_to) 92 | 93 | # Report! 94 | print('') 95 | print('Done!') 96 | print('') 97 | 98 | if __name__ == '__main__': 99 | main() 100 | 101 | -------------------------------------------------------------------------------- /bl3save/cli_import_protobuf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import os 25 | import sys 26 | import argparse 27 | import bl3save 28 | from bl3save.bl3save import BL3Save 29 | 30 | def main(): 31 | 32 | # Set up args 33 | parser = argparse.ArgumentParser( 34 | description='Import BL3 Savegame Protobuf v{}'.format(bl3save.__version__), 35 | ) 36 | 37 | parser.add_argument('-V', '--version', 38 | action='version', 39 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 40 | ) 41 | 42 | parser.add_argument('-p', '--protobuf', 43 | type=str, 44 | required=True, 45 | help='Filename containing protobufs to import') 46 | 47 | parser.add_argument('-t', '--to-filename', 48 | dest='filename_to', 49 | type=str, 50 | required=True, 51 | help='Filename to import protobufs into') 52 | 53 | parser.add_argument('-c', '--clobber', 54 | action='store_true', 55 | help='Clobber (overwrite) files without asking') 56 | 57 | # Parse args 58 | args = parser.parse_args() 59 | 60 | # Make sure that files exist 61 | if not os.path.exists(args.filename_to): 62 | raise Exception('Filename {} does not exist'.format(args.filename_to)) 63 | if not os.path.exists(args.protobuf): 64 | raise Exception('Filename {} does not exist'.format(args.protobuf)) 65 | 66 | # Load the savegame file 67 | save_file = BL3Save(args.filename_to) 68 | 69 | # Load the protobuf file and import (so we know it's valid before 70 | # we ask for confirmation) 71 | with open(args.protobuf, 'rb') as df: 72 | save_file.import_protobuf(df.read()) 73 | 74 | # Ask for confirmation 75 | if not args.clobber: 76 | sys.stdout.write('Really import protobufs from {} into {} [y/N]? '.format( 77 | args.protobuf, 78 | args.filename_to, 79 | )) 80 | sys.stdout.flush() 81 | response = sys.stdin.readline().strip().lower() 82 | if response == 'y': 83 | pass 84 | else: 85 | print('') 86 | print('Aborting!') 87 | print('') 88 | sys.exit(1) 89 | 90 | # ... and save. 91 | save_file.save_to(args.filename_to) 92 | 93 | # Report! 94 | print('') 95 | print('Done!') 96 | print('') 97 | 98 | if __name__ == '__main__': 99 | main() 100 | 101 | -------------------------------------------------------------------------------- /bl3save/cli_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import bl3save 25 | import argparse 26 | import itertools 27 | from bl3save.bl3save import BL3Save 28 | 29 | def main(): 30 | 31 | # Arguments 32 | parser = argparse.ArgumentParser( 33 | description='Borderlands 3 Savegame Info Dumper v{}'.format(bl3save.__version__), 34 | ) 35 | 36 | parser.add_argument('-V', '--version', 37 | action='version', 38 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 39 | ) 40 | 41 | parser.add_argument('-v', '--verbose', 42 | action='store_true', 43 | help='Show all available information', 44 | ) 45 | 46 | parser.add_argument('-i', '--items', 47 | action='store_true', 48 | help='Show inventory items', 49 | ) 50 | 51 | parser.add_argument('--all-missions', 52 | dest='all_missions', 53 | action='store_true', 54 | help='Show all missions') 55 | 56 | parser.add_argument('--mission-paths', 57 | action='store_true', 58 | help='Display raw mission paths when reporting on missions') 59 | 60 | parser.add_argument('--all-challenges', 61 | dest='all_challenges', 62 | action='store_true', 63 | help='Show all challenges') 64 | 65 | parser.add_argument('--fast-travel', 66 | dest='fast_travel', 67 | action='store_true', 68 | help='Show all unlocked Fast Travel stations') 69 | 70 | parser.add_argument('filename', 71 | help='Filename to process', 72 | ) 73 | 74 | args = parser.parse_args() 75 | 76 | # Load the save 77 | save = BL3Save(args.filename) 78 | 79 | # Character name 80 | print('Character: {}'.format(save.get_char_name())) 81 | 82 | # Savegame ID 83 | print('Savegame ID: {}'.format(save.get_savegame_id())) 84 | 85 | # Savegame GUID 86 | print('Savegame GUID: {}'.format(save.get_savegame_guid())) 87 | 88 | # Pet Names 89 | petnames = save.get_pet_names(True) 90 | if len(petnames) > 0: 91 | for (pet_type, pet_name) in petnames.items(): 92 | print(' - {} Name: {}'.format(pet_type, pet_name)) 93 | 94 | # Class 95 | print('Player Class: {}'.format(save.get_class(True))) 96 | 97 | # XP/Level 98 | print('XP: {}'.format(save.get_xp())) 99 | print('Level: {}'.format(save.get_level())) 100 | print('Guardian Rank: {}'.format(save.get_guardian_rank())) 101 | 102 | # Currencies 103 | print('Money: {}'.format(save.get_money())) 104 | print('Eridium: {}'.format(save.get_eridium())) 105 | 106 | # Playthroughs 107 | print('Playthroughs Completed: {}'.format(save.get_playthroughs_completed())) 108 | 109 | # Playthrough-specific Data 110 | for pt, (mayhem, 111 | mayhem_seed, 112 | mapname, 113 | stations, 114 | active_missions, 115 | active_missions_obj, 116 | completed_missions, 117 | completed_missions_obj, 118 | ) in enumerate(itertools.zip_longest( 119 | save.get_pt_mayhem_levels(), 120 | save.get_pt_mayhem_seeds(), 121 | save.get_pt_last_maps(True), 122 | save.get_pt_active_ft_station_lists(), 123 | save.get_pt_active_mission_lists(True), 124 | save.get_pt_active_mission_lists(), 125 | save.get_pt_completed_mission_lists(True), 126 | save.get_pt_completed_mission_lists(), 127 | )): 128 | 129 | print('Playthrough {} Info:'.format(pt+1)) 130 | 131 | # Mayhem 132 | if mayhem is not None: 133 | print(' - Mayhem Level: {}'.format(mayhem)) 134 | if mayhem_seed is not None: 135 | print(' - Mayhem Random Seed: {}'.format(mayhem_seed)) 136 | 137 | # Map 138 | if mapname is not None: 139 | print(' - In Map: {}'.format(mapname)) 140 | 141 | # FT Stations 142 | if args.verbose or args.fast_travel: 143 | if stations is not None: 144 | if len(stations) == 0: 145 | print(' - No Active Fast Travel Stations') 146 | else: 147 | print(' - Active Fast Travel Stations:'.format(pt+1)) 148 | for station in stations: 149 | print(' - {}'.format(station)) 150 | 151 | # Missions 152 | if active_missions is not None: 153 | if len(active_missions) == 0: 154 | print(' - No Active Missions') 155 | else: 156 | print(' - Active Missions:') 157 | for mission, obj_name in sorted(zip(active_missions, active_missions_obj)): 158 | print(' - {}'.format(mission)) 159 | if args.mission_paths: 160 | print(' {}'.format(obj_name)) 161 | 162 | # Completed mission count 163 | if completed_missions is not None: 164 | print(' - Missions completed: {}'.format(len(completed_missions))) 165 | 166 | # Show all missions if need be 167 | if args.verbose or args.all_missions: 168 | for mission, obj_name in sorted(zip(completed_missions, completed_missions_obj)): 169 | print(' - {}'.format(mission)) 170 | if args.mission_paths: 171 | print(' {}'.format(obj_name)) 172 | 173 | # "Important" missions - I'm torn as to whether or not this kind of thing 174 | # should be in bl3save.py itself, or at least some constants in __init__.py 175 | mission_set = set(completed_missions) 176 | importants = [] 177 | if 'Divine Retribution' in mission_set: 178 | importants.append('Main Game') 179 | if 'All Bets Off' in mission_set: 180 | importants.append('DLC1 - Moxxi\'s Heist of the Handsome Jackpot') 181 | if 'The Call of Gythian' in mission_set: 182 | importants.append('DLC2 - Guns, Love, and Tentacles') 183 | if 'Riding to Ruin' in mission_set: 184 | importants.append('DLC3 - Bounty of Blood') 185 | if 'Locus of Rage' in mission_set: 186 | importants.append('DLC4 - Psycho Krieg and the Fantastic Fustercluck') 187 | if "Mysteriouslier: Horror at Scryer's Crypt" in mission_set: 188 | importants.append('DLC6 - Director\'s Cut') 189 | if len(importants) > 0: 190 | print(' - Mission Milestones:') 191 | for important in importants: 192 | print(' - Finished: {}'.format(important)) 193 | 194 | # Inventory Slots that we care about 195 | print('Unlockable Inventory Slots:') 196 | for slot in [bl3save.WEAPON3, bl3save.WEAPON4, bl3save.COM, bl3save.ARTIFACT]: 197 | print(' - {}: {}'.format( 198 | bl3save.slot_to_eng[slot], 199 | save.get_equip_slot(slot).enabled(), 200 | )) 201 | 202 | # Inventory 203 | if args.verbose or args.items: 204 | items = save.get_items() 205 | if len(items) == 0: 206 | print('Nothing in Inventory') 207 | else: 208 | print('Inventory:') 209 | to_report = [] 210 | for item in items: 211 | if item.eng_name: 212 | to_report.append(' - {} ({}): {}'.format(item.eng_name, item.get_level_eng(), item.get_serial_base64())) 213 | else: 214 | to_report.append(' - unknown item: {}'.format(item.get_serial_base64())) 215 | for line in sorted(to_report): 216 | print(line) 217 | 218 | # Equipped Items 219 | if args.verbose or args.items: 220 | items = save.get_equipped_items(True) 221 | if any(items.values()): 222 | print('Equipped Items:') 223 | to_report = [] 224 | for (slot, item) in items.items(): 225 | if item: 226 | if item.eng_name: 227 | to_report.append(' - {}: {} ({}): {}'.format(slot, item.eng_name, item.get_level_eng(), item.get_serial_base64())) 228 | else: 229 | to_report.append(' - {}: unknown item: {}'.format(slot, item.get_serial_base64())) 230 | for line in sorted(to_report): 231 | print(line) 232 | else: 233 | print('No Equipped Items') 234 | 235 | # SDUs 236 | sdus = save.get_sdus_with_max(True) 237 | if len(sdus) == 0: 238 | print('No SDUs Purchased') 239 | else: 240 | print('SDUs:') 241 | for sdu, (count, max_sdus) in sdus.items(): 242 | print(' - {}: {}/{}'.format(sdu, count, max_sdus)) 243 | 244 | # Ammo 245 | print('Ammo Pools:') 246 | for ammo, count in save.get_ammo_counts(True).items(): 247 | print(' - {}: {}'.format(ammo, count)) 248 | 249 | # Challenges 250 | print('Challenges we care about:') 251 | for challenge, status in save.get_interesting_challenges(True).items(): 252 | print(' - {}: {}'.format(challenge, status)) 253 | 254 | # "raw" Challenges 255 | if args.verbose or args.all_challenges: 256 | print('All Challenges:') 257 | for challenge in save.get_all_challenges_raw(): 258 | print(' - {} (Completed: {}, Counter: {}, Progress: {})'.format( 259 | challenge.challenge_class_path, 260 | challenge.currently_completed, 261 | challenge.progress_counter, 262 | challenge.completed_progress_level, 263 | )) 264 | 265 | # Vehicle unlocks 266 | print('Unlocked Vehicle Parts:') 267 | for vehicle, chassis_count in save.get_vehicle_chassis_counts().items(): 268 | eng = bl3save.vehicle_to_eng[vehicle] 269 | print(' - {} - Chassis (wheels): {}/{}, Parts: {}/{}, Skins: {}/{}'.format( 270 | eng, 271 | chassis_count, len(bl3save.vehicle_chassis[vehicle]), 272 | save.get_vehicle_part_count(vehicle), len(bl3save.vehicle_parts[vehicle]), 273 | save.get_vehicle_skin_count(vehicle), len(bl3save.vehicle_skins[vehicle]), 274 | )) 275 | 276 | if __name__ == '__main__': 277 | main() 278 | -------------------------------------------------------------------------------- /bl3save/cli_prof_import_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import os 25 | import sys 26 | import argparse 27 | import bl3save 28 | from bl3save.bl3profile import BL3Profile 29 | 30 | def main(): 31 | 32 | # Set up args 33 | parser = argparse.ArgumentParser( 34 | description='Import BL3 Profile JSON v{}'.format(bl3save.__version__), 35 | ) 36 | 37 | parser.add_argument('-V', '--version', 38 | action='version', 39 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 40 | ) 41 | 42 | parser.add_argument('-j', '--json', 43 | type=str, 44 | required=True, 45 | help='Filename containing JSON to import') 46 | 47 | parser.add_argument('-t', '--to-filename', 48 | dest='filename_to', 49 | type=str, 50 | required=True, 51 | help='Filename to import JSON into') 52 | 53 | parser.add_argument('-c', '--clobber', 54 | action='store_true', 55 | help='Clobber (overwrite) files without asking') 56 | 57 | # Parse args 58 | args = parser.parse_args() 59 | 60 | # Make sure that files exist 61 | if not os.path.exists(args.filename_to): 62 | raise Exception('Filename {} does not exist'.format(args.filename_to)) 63 | if not os.path.exists(args.json): 64 | raise Exception('Filename {} does not exist'.format(args.json)) 65 | 66 | # Load the profile file 67 | prof_file = BL3Profile(args.filename_to) 68 | 69 | # Load the JSON file and import (so we know it's valid before 70 | # we ask for confirmation) 71 | with open(args.json, 'rt') as df: 72 | prof_file.import_json(df.read()) 73 | 74 | # Ask for confirmation 75 | if not args.clobber: 76 | sys.stdout.write('Really import JSON from {} into {} [y/N]? '.format( 77 | args.json, 78 | args.filename_to, 79 | )) 80 | sys.stdout.flush() 81 | response = sys.stdin.readline().strip().lower() 82 | if response == 'y': 83 | pass 84 | else: 85 | print('') 86 | print('Aborting!') 87 | print('') 88 | sys.exit(1) 89 | 90 | # ... and save. 91 | prof_file.save_to(args.filename_to) 92 | 93 | # Report! 94 | print('') 95 | print('Done!') 96 | print('') 97 | 98 | if __name__ == '__main__': 99 | main() 100 | 101 | -------------------------------------------------------------------------------- /bl3save/cli_prof_import_protobuf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import os 25 | import sys 26 | import argparse 27 | import bl3save 28 | from bl3save.bl3profile import BL3Profile 29 | 30 | def main(): 31 | 32 | # Set up args 33 | parser = argparse.ArgumentParser( 34 | description='Import BL3 Profile Protobuf v{}'.format(bl3save.__version__), 35 | ) 36 | 37 | parser.add_argument('-V', '--version', 38 | action='version', 39 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 40 | ) 41 | 42 | parser.add_argument('-p', '--protobuf', 43 | type=str, 44 | required=True, 45 | help='Filename containing protobufs to import') 46 | 47 | parser.add_argument('-t', '--to-filename', 48 | dest='filename_to', 49 | type=str, 50 | required=True, 51 | help='Filename to import protobufs into') 52 | 53 | parser.add_argument('-c', '--clobber', 54 | action='store_true', 55 | help='Clobber (overwrite) files without asking') 56 | 57 | # Parse args 58 | args = parser.parse_args() 59 | 60 | # Make sure that files exist 61 | if not os.path.exists(args.filename_to): 62 | raise Exception('Filename {} does not exist'.format(args.filename_to)) 63 | if not os.path.exists(args.protobuf): 64 | raise Exception('Filename {} does not exist'.format(args.protobuf)) 65 | 66 | # Load the profile file 67 | prof_file = BL3Profile(args.filename_to) 68 | 69 | # Load the protobuf file and import (so we know it's valid before 70 | # we ask for confirmation) 71 | with open(args.protobuf, 'rb') as df: 72 | prof_file.import_protobuf(df.read()) 73 | 74 | # Ask for confirmation 75 | if not args.clobber: 76 | sys.stdout.write('Really import protobufs from {} into {} [y/N]? '.format( 77 | args.protobuf, 78 | args.filename_to, 79 | )) 80 | sys.stdout.flush() 81 | response = sys.stdin.readline().strip().lower() 82 | if response == 'y': 83 | pass 84 | else: 85 | print('') 86 | print('Aborting!') 87 | print('') 88 | sys.exit(1) 89 | 90 | # ... and save. 91 | prof_file.save_to(args.filename_to) 92 | 93 | # Report! 94 | print('') 95 | print('Done!') 96 | print('') 97 | 98 | if __name__ == '__main__': 99 | main() 100 | 101 | -------------------------------------------------------------------------------- /bl3save/cli_prof_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import bl3save 25 | import argparse 26 | import itertools 27 | from bl3save.bl3profile import BL3Profile 28 | 29 | def main(): 30 | 31 | # Arguments 32 | parser = argparse.ArgumentParser( 33 | description='Borderlands 3 Profile Info Dumper v{}'.format(bl3save.__version__), 34 | ) 35 | 36 | parser.add_argument('-V', '--version', 37 | action='version', 38 | version='BL3 CLI SaveEdit v{}'.format(bl3save.__version__), 39 | ) 40 | 41 | parser.add_argument('-v', '--verbose', 42 | action='store_true', 43 | help='Show all available information', 44 | ) 45 | 46 | parser.add_argument('-i', '--items', 47 | action='store_true', 48 | help='Show inventory items', 49 | ) 50 | 51 | parser.add_argument('filename', 52 | help='Filename to process', 53 | ) 54 | 55 | args = parser.parse_args() 56 | 57 | # Load the profile 58 | prof = BL3Profile(args.filename) 59 | 60 | # Golden Keys 61 | print('Keys:') 62 | print(' - Golden Keys: {}'.format(prof.get_golden_keys())) 63 | print(' - Diamond Keys: {}'.format(prof.get_diamond_keys())) 64 | print(' - Vault Card 1 Keys: {}'.format(prof.get_vaultcard1_keys())) 65 | print(' - Vault Card 1 Chests: {}'.format(prof.get_vaultcard1_chests())) 66 | print(' - Vault Card 2 Keys: {}'.format(prof.get_vaultcard2_keys())) 67 | print(' - Vault Card 2 Chests: {}'.format(prof.get_vaultcard2_chests())) 68 | print(' - Vault Card 3 Keys: {}'.format(prof.get_vaultcard3_keys())) 69 | print(' - Vault Card 3 Chests: {}'.format(prof.get_vaultcard3_chests())) 70 | 71 | # Guardian Rank 72 | print('Guardian Rank: {}'.format(prof.get_guardian_rank())) 73 | print('Available GR Tokens: {}'.format(prof.get_guardian_rank_tokens())) 74 | 75 | # Borderlands Science 76 | print("Borderlands Science Level: {}".format(prof.get_borderlands_science_level())) 77 | print("Available BS Tokens: {}".format(prof.get_borderlands_science_tokens())) 78 | 79 | # SDUs 80 | sdus = prof.get_sdus_with_max(True) 81 | if len(sdus) == 0: 82 | print('No SDUs Purchased') 83 | else: 84 | print('SDUs:') 85 | for sdu, (count, max_sdus) in sdus.items(): 86 | print(' - {}: {}/{}'.format(sdu, count, max_sdus)) 87 | 88 | # Bank Items 89 | bank_items = prof.get_bank_items() 90 | print('Items in bank: {}'.format(len(bank_items))) 91 | if args.verbose or args.items: 92 | to_report = [] 93 | for item in bank_items: 94 | if item.eng_name: 95 | to_report.append(' - {} ({}): {}'.format(item.eng_name, item.get_level_eng(), item.get_serial_base64())) 96 | else: 97 | to_report.append(' - unknown item: {}'.format(item.get_serial_base64())) 98 | for line in sorted(to_report): 99 | print(line) 100 | 101 | # Lost Loot Items 102 | lostloot_items = prof.get_lostloot_items() 103 | print('Items in Lost Loot machine: {}'.format(len(lostloot_items))) 104 | if args.verbose or args.items: 105 | to_report = [] 106 | for item in lostloot_items: 107 | if item.eng_name: 108 | to_report.append(' - {} ({}): {}'.format(item.eng_name, item.get_level_eng(), item.get_serial_base64())) 109 | else: 110 | to_report.append(' - unknown item: {}'.format(item.get_serial_base64())) 111 | for line in sorted(to_report): 112 | print(line) 113 | 114 | # Various customizations 115 | for (label, current, maxcount) in [ 116 | ('Character Skins', prof.get_char_skins(), prof.get_char_skins_total()), 117 | ('Character Heads', prof.get_char_heads(), prof.get_char_heads_total()), 118 | ('ECHO Themes', prof.get_echo_themes(), prof.get_echo_themes_total()), 119 | ('Emotes', prof.get_emotes(), prof.get_emotes_total()), 120 | ('Room Decorations', prof.get_room_decos(), prof.get_room_decos_total()), 121 | ('Weapon Skins', prof.get_weapon_skins(), prof.get_weapon_skins_total()), 122 | ('Weapon Trinkets', prof.get_weapon_trinkets(), prof.get_weapon_trinkets_total()), 123 | ]: 124 | print('{} Unlocked: {}/{}'.format(label, len(current), maxcount)) 125 | 126 | if __name__ == '__main__': 127 | main() 128 | -------------------------------------------------------------------------------- /bl3save/resources/.gitignore: -------------------------------------------------------------------------------- 1 | InventorySerialNumberDatabase.dat 2 | Inventory Serial Number Database.json 3 | -------------------------------------------------------------------------------- /bl3save/resources/README.md: -------------------------------------------------------------------------------- 1 | `inventoryserialdb.json.xz` is generated by the script `gen_inventory_db.py` 2 | right in this directory. It needs a vanilla `InventorySerialNumberDatabase.dat` 3 | in the same directory to do its work. (That can be found by unpacking the 4 | BL3 pak files. Note that this file practically always gets updated with 5 | new patches.) 6 | 7 | `balance_name_mapping.json.xz` is generated by the script 8 | `gen_balance_name_mapping.py` which is found in the 9 | [`dataprocessing` directory of my bl3mods area](https://github.com/BLCM/bl3mods/blob/master/Apocalyptech/dataprocessing/gen_balance_name_mapping.py). 10 | It relies on the CSVs used for my balance/part spreadsheets (generated by [`gen_item_balances.py`](https://github.com/BLCM/bl3mods/blob/master/Apocalyptech/dataprocessing/gen_item_balances.py)), so we've got a 11 | chain of data generation going on here. 12 | 13 | `balance_to_inv_key.json.xz` is generated by the script 14 | `gen_balance_to_invpart.py` which is found in the 15 | [`dataprocessing` directory of my bl3mods area](https://github.com/BLCM/bl3mods/blob/master/Apocalyptech/dataprocessing/gen_balance_to_invpart.py). 16 | It relies on my [Borderlands 3 Object Refs](http://apocalyptech.com/games/bl3-refs/) 17 | database to do its thing. 18 | 19 | -------------------------------------------------------------------------------- /bl3save/resources/balance_name_mapping.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apocalyptech/bl3-cli-saveedit/d459b28a7e88f1a5ae0f16118272d8a5383b8b04/bl3save/resources/balance_name_mapping.json.xz -------------------------------------------------------------------------------- /bl3save/resources/balance_to_inv_key.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apocalyptech/bl3-cli-saveedit/d459b28a7e88f1a5ae0f16118272d8a5383b8b04/bl3save/resources/balance_to_inv_key.json.xz -------------------------------------------------------------------------------- /bl3save/resources/gen_inventory_db.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | # Copyright (c) 2020-2021 CJ Kucera (cj@apocalyptech.com) 5 | # 6 | # This software is provided 'as-is', without any express or implied warranty. 7 | # In no event will the authors be held liable for any damages arising from 8 | # the use of this software. 9 | # 10 | # Permission is granted to anyone to use this software for any purpose, 11 | # including commercial applications, and to alter it and redistribute it 12 | # freely, subject to the following restrictions: 13 | # 14 | # 1. The origin of this software must not be misrepresented; you must not 15 | # claim that you wrote the original software. If you use this software in a 16 | # product, an acknowledgment in the product documentation would be 17 | # appreciated but is not required. 18 | # 19 | # 2. Altered source versions must be plainly marked as such, and must not be 20 | # misrepresented as being the original software. 21 | # 22 | # 3. This notice may not be removed or altered from any source distribution. 23 | 24 | import io 25 | import lzma 26 | import json 27 | import codecs 28 | import argparse 29 | from Crypto.Cipher import AES 30 | 31 | # Takes InventorySerialNumberDatabase.dat from inside the BL3 paks and turns 32 | # it into a compressed JSON file suitable for use in our savegame apps. 33 | # At user request, will also write out a version suitable for sending PRs 34 | # to https://github.com/gibbed/Borderlands3Dumps 35 | 36 | # Input/Output parameters 37 | input_file = 'InventorySerialNumberDatabase.dat' 38 | output_file = 'inventoryserialdb.json.xz' 39 | gibbed_file = 'Inventory Serial Number Database.json' 40 | 41 | ### 42 | ### Decryption bit. Thanks to Baysix for this! 43 | ### 44 | 45 | def decrypt(key, data): 46 | cipher = AES.new(key, AES.MODE_ECB) 47 | return cipher.decrypt(data) 48 | 49 | def decrypt_db_file(file_path): 50 | with open(file_path, 'rb') as df: 51 | data = df.read() 52 | key = decrypt(data[:32], data[-32:]) 53 | return decrypt(key, data[:-32]).rstrip(b'\x00') 54 | 55 | ### 56 | ### Arguments 57 | ### 58 | 59 | parser = argparse.ArgumentParser(description='Convert InventorySerialNumberDatabase.dat into JSON') 60 | 61 | parser.add_argument('-g', '--gibbed', 62 | action='store_true', 63 | help='Also generate JSON suitable for sending PRs to Gibbed at https://github.com/gibbed/Borderlands3Dumps') 64 | 65 | args = parser.parse_args() 66 | 67 | ### 68 | ### Do the work 69 | ### 70 | 71 | # Decrypt 72 | df = io.StringIO(decrypt_db_file(input_file).decode('latin1')) 73 | 74 | # Make sure the file header makes sense 75 | header = df.readline().strip() 76 | if header != 'InvSnDb': 77 | raise Exception('Could not find file header') 78 | version_line = df.readline().strip() 79 | if version_line.startswith('FileVersion='): 80 | version = int(version_line.split('=', 1)[1]) 81 | if version != 1: 82 | raise Exception('Unknown serial number: {}'.format(version)) 83 | else: 84 | raise Exception("Didn't find version string") 85 | 86 | # Loop through the file and turn it into JSON 87 | top = {} 88 | cur_class = None 89 | for line in df: 90 | line = line.strip() 91 | (key, value) = line.split('=', 1) 92 | if key == 'Class': 93 | if value in top: 94 | raise Exception('Already found in db: {}'.format(value)) 95 | cur_class = value 96 | print('Processing class: {}'.format(cur_class)) 97 | top[cur_class] = {'versions': [], 'assets': []} 98 | elif key == 'Version': 99 | if not cur_class: 100 | raise Exception('Found version without cur_class') 101 | (vers, bits) = [int(v) for v in value.split(',', 1)] 102 | top[cur_class]['versions'].append({'version': vers, 'bits': bits}) 103 | elif key == 'Asset': 104 | if not cur_class: 105 | raise Exception('Found asset without cur_class') 106 | (uuid, obj_name) = value.split(',', 1) 107 | top[cur_class]['assets'].append(obj_name) 108 | 109 | # Output to compressed JSON 110 | with lzma.open(output_file, 'wt') as odf: 111 | json.dump(top, odf, separators=(',', ':')) 112 | print('') 113 | print('Wrote JSON to {}'.format(output_file)) 114 | 115 | # If we've been asked to, also generate a Gibbed-compatible JSON file, so that 116 | # diffs in that repo are nice and clean. This is pretty stupidly done, but 117 | # whatever -- the format's simple enough. 118 | if args.gibbed: 119 | df = io.StringIO(json.dumps(top, indent=' ')) 120 | with open(gibbed_file, 'wt', encoding='utf-8') as real_df: 121 | real_df.write(codecs.BOM_UTF8.decode('utf-8')) 122 | doing_versions = False 123 | started_version = False 124 | for line in df: 125 | if doing_versions: 126 | if started_version: 127 | if '}' in line: 128 | version_lines.append(line.lstrip()) 129 | real_df.write(''.join(version_lines)) 130 | started_version = False 131 | else: 132 | version_lines.append(line.strip().replace(' ', '')) 133 | elif '{' in line: 134 | version_lines = [line.rstrip()] 135 | started_version = True 136 | else: 137 | doing_versions = False 138 | real_df.write(line) 139 | elif '"versions":' in line: 140 | doing_versions = True 141 | real_df.write(line) 142 | else: 143 | if line == "}\n": 144 | real_df.write('}') 145 | else: 146 | real_df.write(line) 147 | print('') 148 | print('Wrote Gibbed-compatible JSON to {}'.format(gibbed_file)) 149 | 150 | -------------------------------------------------------------------------------- /bl3save/resources/inventoryserialdb.json.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apocalyptech/bl3-cli-saveedit/d459b28a7e88f1a5ae0f16118272d8a5383b8b04/bl3save/resources/inventoryserialdb.json.xz -------------------------------------------------------------------------------- /mod_testing_gear.txt: -------------------------------------------------------------------------------- 1 | # Testing gear used for my mod-testing characters. Intended to be 2 | # used with my "super buff" mods. Note that the only syntax that 3 | # actually matters in this file is the "BL3(foo)" lines; these 4 | # hash-commented lines could just as easily do without the hashes. 5 | 6 | # Crader's EM-P5: 7 | BL3(A/keqg/yMeLAidiwT5/bWQwqG3OuxeEhDH4LZAPWg1a2HG2imaCQ) 8 | 9 | # Transformer: 10 | BL3(AzYihiL46NplGmPm8iw3O1fqwmf9kw==) 11 | 12 | -------------------------------------------------------------------------------- /protobufs/OakProfile.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package OakSave; 3 | import "OakShared.proto"; 4 | message PlayerInputBinding_Button { 5 | string rebind_data_path = 1; 6 | repeated string key_names = 2; 7 | } 8 | message PlayerInputBinding_Axis_Key { 9 | string key_name = 1; 10 | Vec3 scale_3d = 2; 11 | } 12 | message PlayerInputBinding_Axis { 13 | string rebind_data_path = 1; 14 | repeated PlayerInputBinding_Axis_Key keys = 2; 15 | } 16 | message PlayerInputBinding_Category { 17 | string category_data_path = 1; 18 | string context_data_path = 2; 19 | repeated PlayerInputBinding_Button button_bindings = 3; 20 | repeated PlayerInputBinding_Axis axis_bindings = 4; 21 | } 22 | message PlayerInputBindings { 23 | repeated PlayerInputBinding_Category categories = 1; 24 | } 25 | message OakProfileLastInventoryFilterInfo { 26 | string slot_type_id = 1; 27 | int32 last_filter_index = 2; 28 | } 29 | message OakProfileMenuTutorialInfo { 30 | repeated string seen_tutorials = 1; 31 | bool tutorials_disabled = 2; 32 | bool tutorials_allowed_in_non_game_modes = 3; 33 | } 34 | message OakFriendEncounterData { 35 | uint32 num_encounters = 1; 36 | int64 time_last_encounter = 2; 37 | } 38 | message GearSoldByFriendData { 39 | string gear_serial_number = 1; 40 | int32 player_class_identifier_hash = 2; 41 | string friend_net_id = 3; 42 | } 43 | message GuardianRankRewardSaveGameData { 44 | int32 num_tokens = 1; 45 | string reward_data_path = 2; 46 | } 47 | message GuardianRankProfileData { 48 | int32 available_tokens = 1; 49 | repeated GuardianRankRewardSaveGameData rank_rewards = 2; 50 | int32 guardian_rank = 3; 51 | int32 guardian_experience = 4; 52 | int32 guardian_reward_random_seed = 5; 53 | int64 new_guardian_experience = 6; 54 | } 55 | message RecentlyMetPlayer { 56 | string shift_player_id = 1; 57 | string first_party_player_id = 2; 58 | bool show_shift_player_entry = 3; 59 | } 60 | message Profile { 61 | message FriendEncountersEntry { 62 | string key = 1; 63 | OakFriendEncounterData value = 2; 64 | } 65 | bool enable_aim_assist = 1; 66 | bool gamepad_invert_look = 2; 67 | bool gamepad_invert_turn = 3; 68 | bool gamepad_invert_move = 4; 69 | bool gamepad_invert_strafe = 5; 70 | bool enable_vibration = 6; 71 | bool invert_mouse_pitch = 7; 72 | bool enable_mouse_smoothing = 8; 73 | float mouse_scale = 9; 74 | bool show_damage_numbers = 10; 75 | bool show_damage_number_icons = 11; 76 | bool enable_training_messages = 12; 77 | bool show_text_chat = 13; 78 | bool center_crosshair = 14; 79 | bool toggle_sprint = 15; 80 | bool toggle_crouch = 16; 81 | bool censor_content = 17; 82 | float music_volume = 18; 83 | float sound_effects_volume = 19; 84 | float vo_volume = 20; 85 | float voice_volume = 21; 86 | bool enable_optional_vo = 22; 87 | bool push_to_talk = 23; 88 | bool enable_controller_audio = 24; 89 | float speaker_angle_front = 25; 90 | float speaker_angle_side = 26; 91 | float speaker_angle_back = 27; 92 | uint32 speaker_setup = 28; 93 | bool mute_audio_on_focus_loss = 29; 94 | bool hide_strict_nat_help_dialog = 34; 95 | PlayerInputBindings player_input_bindings = 35; 96 | repeated uint32 news_hashes = 36; 97 | uint32 last_used_savegame_id = 37; 98 | int32 gamepad_hip_sensitivity_level = 38; 99 | int32 gamepad_zoomed_sensitivity_level = 39; 100 | int32 gamepad_vehicle_sensitivity_level = 40; 101 | float gamepad_movement_dead_zone_x = 41; 102 | float gamepad_movement_dead_zone_y = 42; 103 | float gamepad_look_dead_zone_inner_x = 43; 104 | float gamepad_look_dead_zone_outer_x = 44; 105 | float gamepad_look_dead_zone_inner_y = 45; 106 | float gamepad_look_dead_zone_outer_y = 46; 107 | float gamepad_vehicle_movement_dead_zone_x = 47; 108 | float gamepad_vehicle_movement_dead_zone_y = 48; 109 | float gamepad_vehicle_look_dead_zone_inner_x = 49; 110 | float gamepad_vehicle_look_dead_zone_outer_x = 50; 111 | float gamepad_vehicle_look_dead_zone_inner_y = 51; 112 | float gamepad_vehicle_look_dead_zone_outer_y = 52; 113 | float gamepad_left_dead_zone_inner = 53; 114 | float gamepad_left_dead_zone_outer = 54; 115 | float gamepad_right_dead_zone_inner = 55; 116 | float gamepad_right_dead_zone_outer = 56; 117 | float gamepad_look_axial_dead_zone_scale = 57; 118 | float gamepad_move_axial_dead_zone_scale = 58; 119 | bool gamepad_use_advanced_hip_aim_settings = 59; 120 | bool gamepad_use_advanced_zoomed_aim_settings = 60; 121 | bool gamepad_use_advanced_vehicle_aim_settings = 61; 122 | float gamepad_hip_yaw_rate = 62; 123 | float gamepad_hip_pitch_rate = 63; 124 | float gamepad_hip_extra_yaw = 64; 125 | float gamepad_hip_extra_pitch = 65; 126 | float gamepad_hip_ramp_up_time = 66; 127 | float gamepad_hip_ramp_up_delay = 67; 128 | float gamepad_zoomed_yaw_rate = 68; 129 | float gamepad_zoomed_pitch_rate = 69; 130 | float gamepad_zoomed_extra_yaw = 70; 131 | float gamepad_zoomed_extra_pitch = 71; 132 | float gamepad_zoomed_ramp_up_time = 72; 133 | float gamepad_zoomed_ramp_up_delay = 73; 134 | float gamepad_vehicle_yaw_rate = 74; 135 | float gamepad_vehicle_pitch_rate = 75; 136 | float gamepad_vehicle_extra_yaw = 76; 137 | float gamepad_vehicle_extra_pitch = 77; 138 | float gamepad_vehicle_ramp_up_time = 78; 139 | float gamepad_vehicle_ramp_up_delay = 79; 140 | bool ironsight_aim_assist = 80; 141 | uint32 walking_joystick_scheme = 81; 142 | uint32 driving_joystick_scheme = 82; 143 | float mouse_ads_scale = 83; 144 | float mouse_vehicle_scale = 84; 145 | bool mouse_ironsight_aim_assist = 85; 146 | uint32 vehicle_input_mode = 86; 147 | bool weapon_aim_toggle = 87; 148 | bool mantle_requires_button = 88; 149 | bool fixed_minimap_rotation = 89; 150 | bool map_invert_pitch = 90; 151 | bool map_invert_yaw = 91; 152 | uint32 difficulty = 92; 153 | bool swap_dual_wield_controls = 93; 154 | float base_fov = 94; 155 | uint32 crosshair_neutral_color_frame = 95; 156 | uint32 crosshair_enemy_color_frame = 96; 157 | uint32 crosshair_ally_color_frame = 97; 158 | bool enable_subtitles = 98; 159 | bool enable_closed_captions = 99; 160 | string last_status_menu_page = 100; 161 | repeated OakProfileLastInventoryFilterInfo inventory_screen_last_filter = 101; 162 | OakProfileMenuTutorialInfo tutorial_info = 102; 163 | uint32 default_network_type = 103; 164 | uint32 default_invite_type = 104; 165 | string matchmaking_region = 105; 166 | uint32 streaming_service = 106; 167 | int32 max_cached_friend_events = 107; 168 | int32 max_cached_friend_statuses = 108; 169 | repeated string friend_events = 109; 170 | repeated string friend_statuses = 110; 171 | int64 last_whisper_fetch_events_time = 111; 172 | int64 last_whisper_fetch_statuses_time = 112; 173 | uint32 desired_crossplay_state = 113; 174 | repeated FriendEncountersEntry friend_encounters = 133; 175 | int32 max_friend_encounter_size = 134; 176 | repeated GameStatSaveGameData profile_stats_data = 135; 177 | repeated InventoryCategorySaveData bank_inventory_category_list = 136; 178 | repeated bytes bank_inventory_list = 137; 179 | repeated bytes lost_loot_inventory_list = 138; 180 | repeated OakMailItem npc_mail_items = 139; 181 | repeated string mail_guids = 140; 182 | repeated string unread_mail_guids = 141; 183 | repeated GearSoldByFriendData gear_sold_by_friends = 142; 184 | repeated OakSDUSaveGameData profile_sdu_list = 143; 185 | repeated OakCustomizationSaveGameData unlocked_customizations = 144; 186 | repeated OakInventoryCustomizationPartInfo unlocked_inventory_customization_parts = 145; 187 | GuardianRankProfileData guardian_rank = 146; 188 | repeated CrewQuartersDecorationItemSaveGameData unlocked_crew_quarters_decorations = 147; 189 | repeated CrewQuartersRoomItemSaveGameData unlocked_crew_quarters_rooms = 148; 190 | bool enable_mouse_acceleration = 150; 191 | bool enable_gamepad_input = 151; 192 | bool use_classic_gamepad_input = 152; 193 | float master_volume = 153; 194 | uint32 monitor_display_type = 154; 195 | uint32 graphics_mode = 155; 196 | uint32 frame_rate_limit = 156; 197 | float base_vehicle_fov = 157; 198 | uint32 graphics_quality = 158; 199 | uint32 anisotropic_filtering = 159; 200 | uint32 shadow_quality = 160; 201 | uint32 display_performance_stats = 161; 202 | uint32 texture_detail = 162; 203 | uint32 draw_distance = 163; 204 | uint32 clutter = 164; 205 | uint32 tessellation = 165; 206 | uint32 foliage = 166; 207 | bool foliage_shadows = 167; 208 | bool planar_reflections = 168; 209 | uint32 volumetric_fog = 169; 210 | uint32 screen_space_reflections = 170; 211 | uint32 character_texture_detail = 171; 212 | uint32 character_detail = 172; 213 | uint32 ambient_occlusion_quality = 173; 214 | bool object_motion_blur = 174; 215 | bool lens_flare = 175; 216 | bool combat_number_long_format = 176; 217 | bool show_minimap_legendaries = 177; 218 | bool use_player_callouts = 178; 219 | uint32 friend_event_notification_lifetime = 179; 220 | uint32 friend_event_notification_frequency = 180; 221 | uint32 trade_request_reception_type = 181; 222 | float head_bob_scale = 182; 223 | bool has_seen_first_boot = 184; 224 | float subs_cc_size = 189; 225 | float cc_subs_background_opacity = 190; 226 | uint32 walking_button_scheme = 191; 227 | uint32 driving_button_scheme = 192; 228 | uint32 glyph_mode = 193; 229 | bool use_MPH = 194; 230 | repeated RegisteredDownloadableEntitlements registered_downloadable_entitlements = 195; 231 | repeated string seen_news_items = 196; 232 | bool auto_centering_enabled = 197; 233 | bool increased_chance_for_subscribers = 198; 234 | bool rare_chest_event_enabled = 199; 235 | bool badass_event_enabled = 200; 236 | bool pinata_event_enabled = 201; 237 | int32 min_time_between_badass_events = 202; 238 | float hud_scale_multiplier = 203; 239 | bool disable_spatial_audio__or__has_reset_console_fov = 204; 240 | int32 total_playtime_seconds = 205; 241 | bool moxxis_drink_event_enabled = 206; 242 | int32 moxxis_drink_event_bits_product_id = 207; 243 | repeated ChallengeSaveGameData challenge_data = 208; 244 | repeated int32 CitizenScienceLevelProgression = 209; 245 | bool default_dead_zone_inner_updated = 210; 246 | bool disable_event_content = 211; 247 | uint32 desired_friend_sync_state = 212; 248 | bool needs_shift_first_boot = 213; 249 | repeated RecentlyMetPlayer recently_met_players = 214; 250 | int32 CitizenScienceActiveBoosterIndex = 215; 251 | float CitizenScienceActiveBoosterRemainingTime = 216; 252 | float CitizenScienceActiveBoosterTotalTime = 217; 253 | int32 StreamerPrimaryActiveBoosterIndex = 218; 254 | float StreamerPrimaryActiveBoosterRemainingTime = 219; 255 | float StreamerPrimaryActiveBoosterTotalTime = 220; 256 | int32 StreamerSecondaryActiveBoosterIndex = 221; 257 | float StreamerSecondaryActiveBoosterRemainingTime = 222; 258 | float StreamerSecondaryActiveBoosterTotalTime = 223; 259 | int32 StreamerBoosterTier = 224; 260 | int32 CitizenScienceCSBucksAmount = 226; 261 | bool bCitizenScienceHasSeenIntroVideo = 227; 262 | bool bCitizenScienceTutorialDone = 228; 263 | bool enable_trigger_feedback = 229; 264 | bool fixed_initial_zonemap_rotation = 230; 265 | VaultCardSaveGameData vault_card = 231; 266 | uint32 player_selected_league = 232; 267 | bool needs_shift_first_boot_primary = 233; 268 | int32 autosell_rarity = 234; 269 | } 270 | -------------------------------------------------------------------------------- /protobufs/OakSave.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package OakSave; 3 | import "OakShared.proto"; 4 | message PlayerClassSaveGameData { 5 | string player_class_path = 1; 6 | uint32 dlc_package_id = 2; 7 | } 8 | message ResourcePoolSavegameData { 9 | float amount = 1; 10 | string resource_path = 2; 11 | } 12 | message RegionSaveGameData { 13 | int32 game_stage = 1; 14 | int32 play_through_idx = 2; 15 | string region_path = 3; 16 | uint32 dlc_package_id = 4; 17 | } 18 | message InventoryBalanceStateInitializationData { 19 | int32 game_stage = 1; 20 | string inventory_data = 2; 21 | string inventory_balance_data = 3; 22 | string manufacturer_data = 4; 23 | repeated string part_list = 5; 24 | repeated string generic_part_list = 6; 25 | bytes additional_data = 7; 26 | repeated string customization_part_list = 8; 27 | } 28 | message OakInventoryItemSaveGameData { 29 | bytes item_serial_number = 1; 30 | int32 pickup_order_index = 2; 31 | int32 flags = 3; 32 | string weapon_skin_path = 4; 33 | InventoryBalanceStateInitializationData development_save_data = 5; 34 | } 35 | message EquippedInventorySaveGameData { 36 | int32 inventory_list_index = 1; 37 | bool enabled = 2; 38 | string slot_data_path = 3; 39 | string trinket_data_path = 4; 40 | } 41 | message OakAbilityTreeItemSaveGameData { 42 | string item_asset_path = 1; 43 | int32 points = 2; 44 | int32 max_points = 3; 45 | int32 tree_identifier = 4; 46 | } 47 | message OakAbilitySlotSaveGameData { 48 | string ability_class_path = 1; 49 | string slot_asset_path = 2; 50 | } 51 | message OakActionAbilityAugmentSaveGameData { 52 | string action_ability_class_path = 1; 53 | string slot_asset_path = 2; 54 | string augment_asset_path = 3; 55 | } 56 | message OakActionAbilityAugmentConfigurationSaveGameData { 57 | string ability_class_path = 1; 58 | string augment_asset_path = 2; 59 | string mod_slot_asset_path = 3; 60 | string mod_asset_path = 4; 61 | } 62 | message OakPlayerAbilitySaveGameData { 63 | int32 ability_points = 1; 64 | repeated OakAbilityTreeItemSaveGameData tree_item_list = 2; 65 | repeated OakAbilitySlotSaveGameData ability_slot_list = 3; 66 | repeated OakActionAbilityAugmentSaveGameData augment_slot_list = 4; 67 | repeated OakActionAbilityAugmentConfigurationSaveGameData augment_configuration_list = 5; 68 | int32 tree_grade = 6; 69 | } 70 | message MissionStatusPlayerSaveGameData { 71 | enum MissionState { 72 | MS_NotStarted = 0; 73 | MS_Active = 1; 74 | MS_Complete = 2; 75 | MS_Failed = 3; 76 | MS_Unknown = 4; 77 | } 78 | MissionState status = 1; 79 | bool has_been_viewed_in_log = 2; 80 | repeated int32 objectives_progress = 3; 81 | string mission_class_path = 4; 82 | string active_objective_set_path = 5; 83 | uint32 dlc_package_id = 6; 84 | bool kickoff_played = 7; 85 | uint32 league_instance = 8; 86 | } 87 | message MissionPlaythroughSaveGameData { 88 | repeated MissionStatusPlayerSaveGameData mission_list = 1; 89 | string tracked_mission_class_path = 2; 90 | } 91 | message ActiveFastTravelSaveData { 92 | string active_travel_station_name = 1; 93 | bool blacklisted = 2; 94 | } 95 | message PlaythroughActiveFastTravelSaveData { 96 | repeated ActiveFastTravelSaveData active_travel_stations = 1; 97 | } 98 | message DiscoveredAreaInfo { 99 | string discovered_area_name = 1; 100 | uint32 discovered_playthroughs = 2; 101 | } 102 | message DiscoveredLevelInfo { 103 | string discovered_level_name = 1; 104 | uint32 discovered_playthroughs = 3; 105 | repeated DiscoveredAreaInfo discovered_area_info = 4; 106 | } 107 | message DiscoveredPlanetInfo { 108 | string discovered_planet = 1; 109 | bool is_new_planet = 2; 110 | } 111 | message DiscoverySaveData { 112 | repeated DiscoveredLevelInfo discovered_level_info = 1; 113 | } 114 | message VehicleUnlockedSaveGameData { 115 | string asset_path = 1; 116 | bool just_unlocked = 2; 117 | } 118 | message OakCARMenuVehicleConfigSaveData { 119 | string loadout_save_name = 1; 120 | string body_asset_path = 2; 121 | string wheel_asset_path = 3; 122 | string armor_asset_path = 4; 123 | string core_mod_asset_path = 5; 124 | string gunner_weapon_asset_path = 6; 125 | string driver_weapon_asset_path = 7; 126 | string ornament_asset_path = 8; 127 | string material_decal_asset_path = 9; 128 | string material_asset_path = 10; 129 | int32 color_index_1 = 11; 130 | int32 color_index_2 = 12; 131 | int32 color_index_3 = 13; 132 | } 133 | message CustomPlayerColorSaveGameData { 134 | string color_parameter = 1; 135 | Vec3 applied_color = 2; 136 | Vec3 split_color = 3; 137 | bool use_default_color = 4; 138 | bool use_default_split_color = 5; 139 | } 140 | message GuardianRankSaveGameData { 141 | int32 guardian_rank = 1; 142 | int32 guardian_experience = 2; 143 | } 144 | message GuardianRankRewardCharacterSaveGameData { 145 | int32 num_tokens = 1; 146 | bool is_enabled = 2; 147 | string reward_data_path = 3; 148 | } 149 | message GuardianRankPerkCharacterSaveGameData { 150 | bool is_enabled = 1; 151 | string perk_data_path = 2; 152 | } 153 | message GuardianRankCharacterSaveGameData { 154 | int32 guardian_available_tokens = 1; 155 | int32 guardian_rank = 2; 156 | int32 guardian_experience = 3; 157 | repeated GuardianRankRewardCharacterSaveGameData rank_rewards = 4; 158 | repeated GuardianRankPerkCharacterSaveGameData rank_perks = 5; 159 | int32 guardian_reward_random_seed = 6; 160 | int64 new_guardian_experience = 7; 161 | bool is_rank_system_enabled = 8; 162 | } 163 | message CrewQuartersDecorationSaveData { 164 | int32 decoration_index = 1; 165 | string decoration_data_path = 2; 166 | } 167 | message CrewQuartersSaveData { 168 | int32 preferred_room_assignment = 1; 169 | repeated CrewQuartersDecorationSaveData decorations = 2; 170 | string room_data_path = 3; 171 | } 172 | message CrewQuartersGunRackItemSaveData { 173 | bytes encrypted_serial_number = 1; 174 | string slot_asset_path = 2; 175 | InventoryBalanceStateInitializationData development_save_data = 3; 176 | } 177 | message CrewQuartersGunRackSaveData { 178 | repeated CrewQuartersGunRackItemSaveData rack_save_data = 1; 179 | } 180 | message EchoLogSaveGameData { 181 | bool has_been_seen_in_log = 1; 182 | string echo_log_path = 2; 183 | } 184 | message MapIDData { 185 | uint32 zone_name_id = 1; 186 | uint32 map_name_id = 2; 187 | } 188 | message GameStateSaveData { 189 | MapIDData last_traveled_map_id = 1; 190 | int32 mayhem_level = 2; 191 | int32 mayhem_random_seed = 3; 192 | } 193 | message ChallengeCategoryProgressSaveData { 194 | bytes category_progress = 1; 195 | } 196 | message OakPlayerCharacterAugmentSaveGameData { 197 | string slot_asset_path = 1; 198 | string augment_asset_path = 2; 199 | } 200 | message OakPlayerCharacterSlotSaveGameData { 201 | repeated OakPlayerCharacterAugmentSaveGameData augment_slot_list = 1; 202 | } 203 | message UITrackingSaveGameData { 204 | bool has_seen_skill_menu_unlock = 1; 205 | bool has_seen_guardian_rank_menu_unlock = 2; 206 | bool has_seen_echo_boot_ammo_bar = 3; 207 | bool has_seen_echo_boot_shield_bar = 4; 208 | bool has_seen_echo_boot_grenades = 5; 209 | int32 highest_thvm_breadcrumb_seen = 6; 210 | repeated string inventory_slot_unlocks_seen = 7; 211 | int32 saved_spin_offset = 8; 212 | } 213 | message PlanetCycleInfo { 214 | string planet_name = 1; 215 | float cycle_length = 2; 216 | float last_cached_time = 3; 217 | } 218 | message TimeOfDaySaveGameData { 219 | repeated PlanetCycleInfo planet_cycle_info = 1; 220 | string planet_cycle = 2; 221 | } 222 | message LevelPersistence_Actor_SaveGameData { 223 | string actor_name = 1; 224 | int32 timer_remaining = 2; 225 | } 226 | message LevelPersistence_Level_SaveGameData { 227 | string level_name = 1; 228 | repeated LevelPersistence_Actor_SaveGameData saved_actors = 2; 229 | } 230 | message GbxZoneMapFODSavedLevelData { 231 | string level_name = 1; 232 | uint32 fod_texture_size = 2; 233 | uint32 num_chunks = 3; 234 | float discovery_percentage = 4; 235 | uint32 data_state = 5; 236 | uint32 data_revision = 6; 237 | bytes fod_data = 7; 238 | } 239 | message GbxZoneMapFODSaveGameData { 240 | repeated GbxZoneMapFODSavedLevelData level_data = 1; 241 | } 242 | message OakProfileCloudData { 243 | repeated GameStatSaveGameData profile_stats_data = 1; 244 | repeated bytes bank_inventory_list = 2; 245 | repeated bytes lost_loot_inventory_list = 3; 246 | repeated OakMailItem npc_mail_items = 4; 247 | repeated OakSDUSaveGameData profile_sdu_list = 5; 248 | repeated OakCustomizationSaveGameData unlocked_customizations = 6; 249 | repeated OakInventoryCustomizationPartInfo unlocked_inventory_customization_parts = 7; 250 | int64 guardian_experience = 8; 251 | repeated CrewQuartersDecorationItemSaveGameData unlocked_crew_quarters_decorations = 9; 252 | repeated CrewQuartersRoomItemSaveGameData unlocked_crew_quarters_rooms = 10; 253 | repeated ChallengeSaveGameData challenge_data = 11; 254 | repeated string mail_guids = 12; 255 | repeated int32 CitizenScienceLevelProgression = 13; 256 | int32 CitizenScienceCSBucksAmount = 14; 257 | VaultCardSaveGameData vault_card = 15; 258 | bool bCitizenScienceHasSeenIntroVideo = 25; 259 | bool bCitizenScienceTutorialDone = 26; 260 | } 261 | message Character { 262 | message NicknameMappingsEntry { 263 | string key = 1; 264 | string value = 2; 265 | } 266 | message ActiveLeagueInstanceForEventEntry { 267 | uint32 key = 1; 268 | uint32 value = 2; 269 | } 270 | uint32 save_game_id = 1; 271 | int64 last_save_timestamp = 2; 272 | uint32 time_played_seconds = 3; 273 | PlayerClassSaveGameData player_class_data = 4; 274 | repeated ResourcePoolSavegameData resource_pools = 5; 275 | repeated RegionSaveGameData saved_regions = 6; 276 | int32 experience_points = 7; 277 | repeated GameStatSaveGameData game_stats_data = 8; 278 | repeated InventoryCategorySaveData inventory_category_list = 9; 279 | repeated OakInventoryItemSaveGameData inventory_items = 10; 280 | repeated EquippedInventorySaveGameData equipped_inventory_list = 11; 281 | repeated int32 active_weapon_list = 12; 282 | OakPlayerAbilitySaveGameData ability_data = 13; 283 | int32 last_play_through_index = 14; 284 | int32 playthroughs_completed = 15; 285 | bool show_new_playthrough_notification = 16; 286 | repeated MissionPlaythroughSaveGameData mission_playthroughs_data = 17; 287 | repeated string active_travel_stations = 21; 288 | DiscoverySaveData discovery_data = 22; 289 | string last_active_travel_station = 23; 290 | repeated VehicleUnlockedSaveGameData vehicles_unlocked_data = 24; 291 | repeated string vehicle_parts_unlocked = 25; 292 | repeated OakCARMenuVehicleConfigSaveData vehicle_loadouts = 26; 293 | int32 vehicle_last_loadout_index = 27; 294 | repeated ChallengeSaveGameData challenge_data = 28; 295 | repeated OakSDUSaveGameData sdu_list = 29; 296 | repeated string selected_customizations = 30; 297 | repeated int32 equipped_emote_customizations = 31; 298 | repeated CustomPlayerColorSaveGameData selected_color_customizations = 32; 299 | GuardianRankSaveGameData guardian_rank = 33; 300 | CrewQuartersSaveData crew_quarters_room = 34; 301 | CrewQuartersGunRackSaveData crew_quarters_gun_rack = 35; 302 | repeated EchoLogSaveGameData unlocked_echo_logs = 36; 303 | bool has_played_special_echo_log_insert_already = 37; 304 | repeated NicknameMappingsEntry nickname_mappings = 38; 305 | MapIDData last_traveled_map_id = 39; 306 | ChallengeCategoryProgressSaveData challenge_category_completion_pcts = 40; 307 | OakPlayerCharacterSlotSaveGameData character_slot_save_game_data = 41; 308 | UITrackingSaveGameData ui_tracking_save_game_data = 42; 309 | string preferred_character_name = 43; 310 | int32 name_character_limit = 44; 311 | uint32 preferred_group_mode = 45; 312 | TimeOfDaySaveGameData time_of_day_save_game_data = 46; 313 | repeated LevelPersistence_Level_SaveGameData level_persistence_data = 47; 314 | uint32 accumulated_level_persistence_reset_timer_seconds = 48; 315 | uint32 mayhem_level = 49; 316 | GbxZoneMapFODSaveGameData gbx_zone_map_fod_save_game_data = 50; 317 | repeated ActiveFastTravelSaveData active_or_blacklisted_travel_stations = 51; 318 | repeated string last_active_travel_station_for_playthrough = 52; 319 | repeated GameStateSaveData game_state_save_data_for_playthrough = 53; 320 | repeated RegisteredDownloadableEntitlements registered_downloadable_entitlements = 54; 321 | repeated PlaythroughActiveFastTravelSaveData active_travel_stations_for_playthrough = 55; 322 | string save_game_guid = 56; 323 | GuardianRankCharacterSaveGameData guardian_rank_character_data = 57; 324 | bool optional_objective_reward_fixup_applied = 58; 325 | bool vehicle_part_rewards_fixup_applied = 59; 326 | uint32 last_active_league = 60; 327 | uint32 last_active_league_instance = 61; 328 | repeated ActiveLeagueInstanceForEventEntry active_league_instance_for_event = 62; 329 | bool levelled_save_vehicle_part_rewards_fixup_applied = 63; 330 | OakProfileCloudData profile_cloud_data = 64; 331 | } 332 | -------------------------------------------------------------------------------- /protobufs/OakShared.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package OakSave; 3 | message Vec3 { 4 | float x = 1; 5 | float y = 2; 6 | float z = 3; 7 | } 8 | message GameStatSaveGameData { 9 | int32 stat_value = 1; 10 | string stat_path = 2; 11 | } 12 | message InventoryCategorySaveData { 13 | uint32 base_category_definition_hash = 1; 14 | int32 quantity = 2; 15 | } 16 | message OakSDUSaveGameData { 17 | int32 sdu_level = 1; 18 | string sdu_data_path = 2; 19 | } 20 | message RegisteredDownloadableEntitlement { 21 | int32 id = 1; 22 | uint32 consumed = 2; 23 | bool registered = 3; 24 | bool seen = 4; 25 | } 26 | message RegisteredDownloadableEntitlements { 27 | string entitlement_source_asset_path = 1; 28 | repeated int64 entitlement_ids = 2; 29 | repeated RegisteredDownloadableEntitlement entitlements = 3; 30 | } 31 | message ChallengeStatSaveGameData { 32 | int32 current_stat_value = 1; 33 | string challenge_stat_path = 2; 34 | } 35 | message OakChallengeRewardSaveGameData { 36 | bool challenge_reward_claimed = 1; 37 | } 38 | message ChallengeSaveGameData { 39 | int32 completed_count = 1; 40 | bool is_active = 2; 41 | bool currently_completed = 3; 42 | int32 completed_progress_level = 4; 43 | int32 progress_counter = 5; 44 | repeated ChallengeStatSaveGameData stat_instance_state = 6; 45 | string challenge_class_path = 7; 46 | repeated OakChallengeRewardSaveGameData challenge_reward_info = 8; 47 | } 48 | message OakMailItem { 49 | uint32 mail_item_type = 1; 50 | string sender_display_name = 2; 51 | string subject = 3; 52 | string body = 4; 53 | string gear_serial_number = 5; 54 | string mail_guid = 6; 55 | int64 date_sent = 7; 56 | int64 expiration_date = 8; 57 | string from_player_id = 9; 58 | bool has_been_read = 10; 59 | } 60 | message OakCustomizationSaveGameData { 61 | bool is_new = 1; 62 | string customization_asset_path = 2; 63 | } 64 | message OakInventoryCustomizationPartInfo { 65 | uint32 customization_part_hash = 1; 66 | bool is_new = 2; 67 | } 68 | message CrewQuartersDecorationItemSaveGameData { 69 | bool is_new = 1; 70 | string decoration_item_asset_path = 2; 71 | } 72 | message CrewQuartersRoomItemSaveGameData { 73 | bool is_new = 1; 74 | string room_item_asset_path = 2; 75 | } 76 | message VaultCardSaveGameData { 77 | uint32 last_active_vault_card_id = 2; 78 | int32 current_day_seed = 3; 79 | int32 current_week_seed = 4; 80 | repeated VaultCardPreviousChallenge vault_card_previous_challenges = 5; 81 | repeated VaultCardRewardList vault_card_claimed_rewards = 6; 82 | } 83 | message VaultCardReward { 84 | int32 column_index = 1; 85 | int32 row_index = 2; 86 | } 87 | message VaultCardGearReward { 88 | int32 gear_index = 1; 89 | uint32 repurchase_count = 2; 90 | } 91 | message VaultCardRewardList { 92 | uint32 vault_card_id = 1; 93 | int64 vault_card_experience = 2; 94 | repeated VaultCardReward unlocked_reward_list = 4; 95 | repeated VaultCardReward redeemed_reward_list = 5; 96 | int32 vault_card_chests = 7; 97 | uint32 vault_card_chests_opened = 8; 98 | uint32 vault_card_keys_spent = 9; 99 | repeated VaultCardGearReward gear_rewards = 10; 100 | } 101 | message VaultCardPreviousChallenge { 102 | int32 previous_challenge_seed = 1; 103 | uint32 previous_challenge_id = 2; 104 | } 105 | -------------------------------------------------------------------------------- /protobufs/README.md: -------------------------------------------------------------------------------- 1 | # Borderlands 3 `.proto`s 2 | 3 | [protobuf](https://developers.google.com/protocol-buffers/) message definitions for Borderlands 3. 4 | 5 | These were taken from Gibbed's Github repository here: https://github.com/gibbed/Borderlands3Protos 6 | They can also be extracted directly from `Borderlands3.exe` with 7 | [protodec](https://github.com/schdub/protodec). I've converted the output from that 8 | app into protobuf v3, and done a few other formatting tweaks as well. 9 | 10 | Many thanks to Gibbed for showing us how to get at that data! 11 | 12 | These were compiled into Python classes using the `protoc` utility, as recommended by 13 | [Google's docs](https://developers.google.com/protocol-buffers/docs/pythontutorial): 14 | 15 | protoc --python_out=../bl3save *.proto 16 | 17 | After generation, I did tweak the `import` statements slightly so they would work 18 | inside my `bl3save` package. 19 | 20 | -------------------------------------------------------------------------------- /release.txt: -------------------------------------------------------------------------------- 1 | Things to check before releasing: 2 | 3 | * version in bl3save/__init__.py 4 | * Finalized Changelog entries (update the release date!) 5 | * CHECK THE TODO LIST, PRUNE IF NECESSARY 6 | 7 | To build: 8 | 9 | git tag vx.x.x 10 | git push --tags 11 | python setup.py sdist 12 | python setup.py bdist_wheel 13 | 14 | Then to upload: 15 | 16 | twine upload dist/*x.x.x* 17 | 18 | -------------------------------------------------------------------------------- /requirements-build.txt: -------------------------------------------------------------------------------- 1 | twine 2 | wheel 3 | setuptools 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | protobuf ~= 5.0, >= 5.27.3 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set expandtab tabstop=4 shiftwidth=4: 3 | 4 | import os 5 | from setuptools import find_packages, setup 6 | from bl3save import __version__ 7 | 8 | def readme(): 9 | with open('README.md') as f: 10 | return f.read() 11 | 12 | app_name = 'bl3-cli-saveedit' 13 | 14 | setup( 15 | name=app_name, 16 | version=__version__, 17 | packages=find_packages(), 18 | license='zlib/libpng', 19 | description='Borderlands 3 Savegame Editor', 20 | long_description=readme(), 21 | long_description_content_type='text/markdown', 22 | url='https://github.com/apocalyptech/bl3-cli-saveedit', 23 | author='CJ Kucera', 24 | author_email='cj@apocalyptech.com', 25 | data_files=[ 26 | # I always like these to be installed along with the apps 27 | (f'share/{app_name}', ['COPYING.txt', 'README.md', 'README-saves.md', 'README-profile.md']), 28 | # Seems helpful to bundle the Protobuf definitions (via Gibbed) in here 29 | (f'share/{app_name}/protobufs', [os.path.join('protobufs', f) for f in sorted(os.listdir('protobufs'))]), 30 | # Seems less helpful to package my mod testing gear, but whatever. 31 | (f'share/{app_name}/item_exports', ['mod_testing_gear.txt']), 32 | ], 33 | package_data={ 34 | 'bl3save': [ 35 | 'resources/inventoryserialdb.json.xz', 36 | 'resources/balance_name_mapping.json.xz', 37 | 'resources/balance_to_inv_key.json.xz', 38 | ], 39 | }, 40 | # We now make use of importlib.resources.files, which was added in 3.9: 41 | # https://docs.python.org/3/library/importlib.resources.html#importlib.resources.files 42 | python_requires='>=3.9', 43 | install_requires=[ 44 | 'protobuf ~= 5.0, >= 5.27.3', 45 | ], 46 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 47 | classifiers=[ 48 | 'Development Status :: 5 - Production/Stable', 49 | 'Environment :: Console', 50 | 'Intended Audience :: End Users/Desktop', 51 | 'License :: OSI Approved :: zlib/libpng License', 52 | 'Natural Language :: English', 53 | 'Operating System :: OS Independent', 54 | 'Programming Language :: Python :: 3', 55 | 'Topic :: Games/Entertainment :: First Person Shooters', 56 | 'Topic :: Utilities', 57 | ], 58 | entry_points={ 59 | 'console_scripts': [ 60 | 61 | # Savegame-related scripts 62 | 'bl3-save-edit = bl3save.cli_edit:main', 63 | 'bl3-save-info = bl3save.cli_info:main', 64 | 'bl3-save-import-protobuf = bl3save.cli_import_protobuf:main', 65 | 'bl3-save-import-json = bl3save.cli_import_json:main', 66 | 'bl3-process-archive-saves = bl3save.cli_archive:main', 67 | # Actually, gonna omit this one. Without transferring a lot of other data, 68 | # this can make things a bit weird, and at that point you may as well just 69 | # copy the savegame and alter other bits about it. 70 | #'bl3-save-copy-pt = bl3save.cli_copy_pt:main', 71 | 72 | # Profile-related scripts 73 | 'bl3-profile-edit = bl3save.cli_prof_edit:main', 74 | 'bl3-profile-info = bl3save.cli_prof_info:main', 75 | 'bl3-profile-import-protobuf = bl3save.cli_prof_import_protobuf:main', 76 | 'bl3-profile-import-json = bl3save.cli_prof_import_json:main', 77 | ], 78 | }, 79 | ) 80 | --------------------------------------------------------------------------------