Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90a6ab1ba8 | |||
| 2c795a335f | |||
| 015a13eb86 | |||
| b0524c0589 | |||
| abd3c18040 | |||
| afb4a9c157 | |||
| 9c83499f0f | |||
| cd6d8df243 | |||
| 64ded73f89 | |||
| 022c67cab9 | |||
| cc07cd139c | |||
| 490feb1be7 | |||
| b5a8477ba2 | |||
| e933515e45 | |||
| ff7284dfbf | |||
| 7a710f2e17 | |||
| b36d47a8fb | |||
| 6e2c709426 | |||
| 8929c0e39b | |||
| ab2385fc2f | |||
| 510241e311 | |||
| 5e701e1e1b | |||
| edc4a8b500 | |||
| 7e878a1ac1 | |||
| 5f7ab419b0 | |||
| 38b98bc3ab | |||
| fd0f55bd5e | |||
| 502a35357c | |||
| f3020200a0 | |||
| 492677d5bb | |||
| a325ee5fb5 | |||
| 2812d225cd | |||
| 603627bd33 | |||
| 698ad23963 | |||
| 90a12905e4 | |||
| a20168a347 | |||
| bd3f144c6b | |||
| 2638344649 | |||
| 3d307746da | |||
| 9cb6d5a058 | |||
| 2d077e8b63 | |||
| ad244ea8b0 |
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
[submodule "vendor/nerutils"]
|
[submodule "vendor/nerutils"]
|
||||||
path = vendor/nerutils
|
path = vendor/nerutils
|
||||||
url = https://git.neru.rip/neru/nerutils
|
url = https://git.neru.rip/neru/nerutils
|
||||||
[submodule "vendor/simdjson"]
|
|
||||||
path = vendor/simdjson
|
|
||||||
url = https://github.com/simdjson/simdjson
|
|
||||||
[submodule "vendor/CUE4Parse"]
|
[submodule "vendor/CUE4Parse"]
|
||||||
path = vendor/CUE4Parse
|
path = vendor/CUE4Parse
|
||||||
url = https://github.com/FabianFG/CUE4Parse
|
url = https://github.com/FabianFG/CUE4Parse
|
||||||
|
[submodule "vendor/json"]
|
||||||
|
path = vendor/json
|
||||||
|
url = https://github.com/nlohmann/json
|
||||||
|
|||||||
+1
-14
@@ -66,19 +66,6 @@ else()
|
|||||||
target_compile_options(dbd-unlocker-warnings INTERFACE -Wall -Wextra -Werror)
|
target_compile_options(dbd-unlocker-warnings INTERFACE -Wall -Wextra -Werror)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ------------------------------
|
|
||||||
# simdjson without errors
|
|
||||||
# ------------------------------
|
|
||||||
add_library(simdjson-wrapped INTERFACE)
|
|
||||||
target_link_libraries(simdjson-wrapped INTERFACE simdjson)
|
|
||||||
|
|
||||||
if(CMAKE_GENERATOR MATCHES "Visual Studio")
|
|
||||||
target_compile_options(simdjson-wrapped INTERFACE /W0)
|
|
||||||
else()
|
|
||||||
target_compile_options(simdjson-wrapped INTERFACE -w)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# unlocker
|
# unlocker
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
@@ -89,7 +76,7 @@ add_executable(dbd-unlocker ${UNLOCKER_SOURCES})
|
|||||||
string(RANDOM LENGTH 12 ALPHABET "abcdefghijklmnopqrstuvwxyz0123456789" RANDOM_EXE_NAME)
|
string(RANDOM LENGTH 12 ALPHABET "abcdefghijklmnopqrstuvwxyz0123456789" RANDOM_EXE_NAME)
|
||||||
set_target_properties(dbd-unlocker PROPERTIES OUTPUT_NAME "${RANDOM_EXE_NAME}")
|
set_target_properties(dbd-unlocker PROPERTIES OUTPUT_NAME "${RANDOM_EXE_NAME}")
|
||||||
|
|
||||||
target_link_libraries(dbd-unlocker PRIVATE dbd-unlocker-warnings OpenSSL::SSL OpenSSL::Crypto nerutils wsock32 ws2_32 wininet crypt32 simdjson-wrapped)
|
target_link_libraries(dbd-unlocker PRIVATE dbd-unlocker-warnings OpenSSL::SSL OpenSSL::Crypto nerutils wsock32 ws2_32 wininet crypt32 nlohmann_json)
|
||||||
|
|
||||||
if(NOT MSVC)
|
if(NOT MSVC)
|
||||||
target_link_options(dbd-unlocker PRIVATE -static -static-libgcc -static-libstdc++)
|
target_link_options(dbd-unlocker PRIVATE -static -static-libgcc -static-libstdc++)
|
||||||
|
|||||||
+73
-73
@@ -1,77 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Items": [
|
"Slashers": [
|
||||||
"Addon_Firecracker_BlackPowder",
|
|
||||||
"Addon_Firecracker_BuckShot",
|
|
||||||
"Addon_Firecracker_FlashPowder",
|
|
||||||
"Addon_Firecracker_GunPowder",
|
|
||||||
"Addon_Firecracker_LargePack",
|
|
||||||
"Addon_Firecracker_LongFuse",
|
|
||||||
"Addon_Firecracker_MagnesiumPowder",
|
|
||||||
"Addon_Firecracker_MediumFuse",
|
|
||||||
"Addon_Flashlight_001",
|
|
||||||
"Addon_Flashlight_002",
|
|
||||||
"Addon_Flashlight_003",
|
|
||||||
"Addon_Flashlight_004",
|
|
||||||
"Addon_Flashlight_005",
|
|
||||||
"Addon_Flashlight_006",
|
|
||||||
"Addon_Flashlight_007",
|
|
||||||
"Addon_Flashlight_008",
|
|
||||||
"Addon_Flashlight_BrokenBulb",
|
|
||||||
"ADDON_flashlight_highendsapphire",
|
|
||||||
"ADDON_flashlight_intensehalogen",
|
|
||||||
"ADDON_flashlight_lonflifebattery",
|
|
||||||
"ADDON_flashlight_oddbulb",
|
|
||||||
"ADDON_flashlight_rubbergrip",
|
|
||||||
"Addon_FogVial_Accelerant",
|
|
||||||
"Addon_FogVial_Amplifier",
|
|
||||||
"Addon_FogVial_DenseFogExtract",
|
|
||||||
"Addon_FogVial_LargeVial",
|
|
||||||
"Addon_FogVial_SlowRelease",
|
|
||||||
"Addon_Key_001",
|
|
||||||
"Addon_Key_002",
|
|
||||||
"Addon_Key_003",
|
|
||||||
"Addon_Key_004",
|
|
||||||
"Addon_Key_005",
|
|
||||||
"Addon_Key_006",
|
|
||||||
"Addon_Key_007",
|
|
||||||
"Addon_Key_008",
|
|
||||||
"Addon_Key_WeddingRing",
|
|
||||||
"Addon_Map_001",
|
|
||||||
"Addon_Map_002",
|
|
||||||
"Addon_Map_003",
|
|
||||||
"Addon_Map_004",
|
|
||||||
"Addon_Map_005",
|
|
||||||
"Addon_Map_006",
|
|
||||||
"Addon_Map_007",
|
|
||||||
"Addon_Map_008",
|
|
||||||
"Addon_Map_009",
|
|
||||||
"Addon_Medkit_001",
|
|
||||||
"Addon_Medkit_002",
|
|
||||||
"Addon_Medkit_003",
|
|
||||||
"Addon_Medkit_004",
|
|
||||||
"Addon_Medkit_005",
|
|
||||||
"Addon_Medkit_006",
|
|
||||||
"Addon_Medkit_007",
|
|
||||||
"Addon_Medkit_008",
|
|
||||||
"ADDON_medkit_abdominaldressing",
|
|
||||||
"Addon_Medkit_BlightedSyringe",
|
|
||||||
"ADDON_medkit_gauzeroll",
|
|
||||||
"ADDON_medkit_needleandthread",
|
|
||||||
"ADDON_medkit_selfadherentwrap",
|
|
||||||
"ADDON_medkit_surgicalsuture",
|
|
||||||
"Addon_Toolbox_001",
|
|
||||||
"Addon_Toolbox_002",
|
|
||||||
"Addon_Toolbox_003",
|
|
||||||
"Addon_Toolbox_004",
|
|
||||||
"Addon_Toolbox_005",
|
|
||||||
"Addon_Toolbox_006",
|
|
||||||
"Addon_Toolbox_007",
|
|
||||||
"Addon_Toolbox_008",
|
|
||||||
"ADDON_toolbox_cuttingwire",
|
|
||||||
"ADDON_toolbox_socketswivels",
|
|
||||||
"ADDON_toolbox_springclamp"
|
|
||||||
],
|
|
||||||
"Powers": [
|
|
||||||
"Addon_Beartrap_001",
|
"Addon_Beartrap_001",
|
||||||
"Addon_Beartrap_002",
|
"Addon_Beartrap_002",
|
||||||
"Addon_Beartrap_003",
|
"Addon_Beartrap_003",
|
||||||
@@ -916,5 +844,77 @@
|
|||||||
"Addon_Trickster_18",
|
"Addon_Trickster_18",
|
||||||
"Addon_Trickster_19",
|
"Addon_Trickster_19",
|
||||||
"Addon_Trickster_20"
|
"Addon_Trickster_20"
|
||||||
|
],
|
||||||
|
"Campers": [
|
||||||
|
"Addon_Firecracker_BlackPowder",
|
||||||
|
"Addon_Firecracker_BuckShot",
|
||||||
|
"Addon_Firecracker_FlashPowder",
|
||||||
|
"Addon_Firecracker_GunPowder",
|
||||||
|
"Addon_Firecracker_LargePack",
|
||||||
|
"Addon_Firecracker_LongFuse",
|
||||||
|
"Addon_Firecracker_MagnesiumPowder",
|
||||||
|
"Addon_Firecracker_MediumFuse",
|
||||||
|
"Addon_Flashlight_001",
|
||||||
|
"Addon_Flashlight_002",
|
||||||
|
"Addon_Flashlight_003",
|
||||||
|
"Addon_Flashlight_004",
|
||||||
|
"Addon_Flashlight_005",
|
||||||
|
"Addon_Flashlight_006",
|
||||||
|
"Addon_Flashlight_007",
|
||||||
|
"Addon_Flashlight_008",
|
||||||
|
"Addon_Flashlight_BrokenBulb",
|
||||||
|
"ADDON_flashlight_highendsapphire",
|
||||||
|
"ADDON_flashlight_intensehalogen",
|
||||||
|
"ADDON_flashlight_lonflifebattery",
|
||||||
|
"ADDON_flashlight_oddbulb",
|
||||||
|
"ADDON_flashlight_rubbergrip",
|
||||||
|
"Addon_FogVial_Accelerant",
|
||||||
|
"Addon_FogVial_Amplifier",
|
||||||
|
"Addon_FogVial_DenseFogExtract",
|
||||||
|
"Addon_FogVial_LargeVial",
|
||||||
|
"Addon_FogVial_SlowRelease",
|
||||||
|
"Addon_Key_001",
|
||||||
|
"Addon_Key_002",
|
||||||
|
"Addon_Key_003",
|
||||||
|
"Addon_Key_004",
|
||||||
|
"Addon_Key_005",
|
||||||
|
"Addon_Key_006",
|
||||||
|
"Addon_Key_007",
|
||||||
|
"Addon_Key_008",
|
||||||
|
"Addon_Key_WeddingRing",
|
||||||
|
"Addon_Map_001",
|
||||||
|
"Addon_Map_002",
|
||||||
|
"Addon_Map_003",
|
||||||
|
"Addon_Map_004",
|
||||||
|
"Addon_Map_005",
|
||||||
|
"Addon_Map_006",
|
||||||
|
"Addon_Map_007",
|
||||||
|
"Addon_Map_008",
|
||||||
|
"Addon_Map_009",
|
||||||
|
"Addon_Medkit_001",
|
||||||
|
"Addon_Medkit_002",
|
||||||
|
"Addon_Medkit_003",
|
||||||
|
"Addon_Medkit_004",
|
||||||
|
"Addon_Medkit_005",
|
||||||
|
"Addon_Medkit_006",
|
||||||
|
"Addon_Medkit_007",
|
||||||
|
"Addon_Medkit_008",
|
||||||
|
"ADDON_medkit_abdominaldressing",
|
||||||
|
"Addon_Medkit_BlightedSyringe",
|
||||||
|
"ADDON_medkit_gauzeroll",
|
||||||
|
"ADDON_medkit_needleandthread",
|
||||||
|
"ADDON_medkit_selfadherentwrap",
|
||||||
|
"ADDON_medkit_surgicalsuture",
|
||||||
|
"Addon_Toolbox_001",
|
||||||
|
"Addon_Toolbox_002",
|
||||||
|
"Addon_Toolbox_003",
|
||||||
|
"Addon_Toolbox_004",
|
||||||
|
"Addon_Toolbox_005",
|
||||||
|
"Addon_Toolbox_006",
|
||||||
|
"Addon_Toolbox_007",
|
||||||
|
"Addon_Toolbox_008",
|
||||||
|
"ADDON_toolbox_cuttingwire",
|
||||||
|
"ADDON_toolbox_socketswivels",
|
||||||
|
"ADDON_toolbox_springclamp"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13
-1
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"Camper": {
|
||||||
"Items": [
|
"Items": [
|
||||||
|
"Father_Key_Card",
|
||||||
"FireflyLantern",
|
"FireflyLantern",
|
||||||
"Item_Camper_AlexsToolbox",
|
"Item_Camper_AlexsToolbox",
|
||||||
"Item_Camper_AnniversaryToolbox",
|
"Item_Camper_AnniversaryToolbox",
|
||||||
@@ -24,6 +26,11 @@
|
|||||||
"Item_Camper_JerryCan_Spring2025",
|
"Item_Camper_JerryCan_Spring2025",
|
||||||
"Item_Camper_K29InfectionRemover",
|
"Item_Camper_K29InfectionRemover",
|
||||||
"Item_Camper_K32Emp",
|
"Item_Camper_K32Emp",
|
||||||
|
"Item_Camper_K33Turret",
|
||||||
|
"Item_Camper_K36MagicItem_Boots",
|
||||||
|
"Item_Camper_K36MagicItem_Bracers",
|
||||||
|
"Item_Camper_K36MagicItem_VecnaEye",
|
||||||
|
"Item_Camper_K36MagicItem_VecnaHand",
|
||||||
"Item_Camper_Key",
|
"Item_Camper_Key",
|
||||||
"Item_Camper_Key_Random",
|
"Item_Camper_Key_Random",
|
||||||
"Item_Camper_LunarToolbox",
|
"Item_Camper_LunarToolbox",
|
||||||
@@ -46,13 +53,17 @@
|
|||||||
"Item_Camper_VoidBomb_Halloween2024",
|
"Item_Camper_VoidBomb_Halloween2024",
|
||||||
"Item_Camper_WornoutToolbox",
|
"Item_Camper_WornoutToolbox",
|
||||||
"Item_FragileObject",
|
"Item_FragileObject",
|
||||||
|
"Item_LamentConfiguration",
|
||||||
"Item_Survivor_CalamariContaminationAntidote",
|
"Item_Survivor_CalamariContaminationAntidote",
|
||||||
"Item_Survivor_K41Mushroom",
|
"Item_Survivor_K41Mushroom",
|
||||||
"Item_Survivor_MakeshiftFogVial",
|
"Item_Survivor_MakeshiftFogVial",
|
||||||
"Item_Survivor_PrototypeFogVial",
|
"Item_Survivor_PrototypeFogVial",
|
||||||
"Item_Survivor_VigosFogVial"
|
"Item_Survivor_VigosFogVial"
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
"Slasher": {
|
||||||
"Powers": [
|
"Powers": [
|
||||||
|
"Item_Blighted_Serum",
|
||||||
"Item_Slasher_Beartrap",
|
"Item_Slasher_Beartrap",
|
||||||
"Item_Slasher_Blinker",
|
"Item_Slasher_Blinker",
|
||||||
"Item_Slasher_Chainsaw",
|
"Item_Slasher_Chainsaw",
|
||||||
@@ -97,3 +108,4 @@
|
|||||||
"Item_Slasher_TormentMode"
|
"Item_Slasher_TormentMode"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+96
-92
@@ -1,22 +1,12 @@
|
|||||||
[
|
{
|
||||||
"BoneSplinter",
|
"Slashers": [
|
||||||
"ArdentRavenWreath",
|
"ArdentRavenWreath",
|
||||||
"ArdentShrikeWreath",
|
"ArdentShrikeWreath",
|
||||||
"ArdentSpottedowlWreath",
|
"ArdentSpottedowlWreath",
|
||||||
"ArdentTanagerWreath",
|
"ArdentTanagerWreath",
|
||||||
"AzarovKey",
|
"BlackSplinter",
|
||||||
"BlackSaltStatuette",
|
|
||||||
"BlackWard",
|
"BlackWard",
|
||||||
"BogLaurelSachet",
|
"BoneSplinter",
|
||||||
"BoundEnvelope",
|
|
||||||
"CattleTag28",
|
|
||||||
"ChalkPouch",
|
|
||||||
"CharredWeddingPhotograph",
|
|
||||||
"ClearReagent",
|
|
||||||
"ColdwindCattleTag81",
|
|
||||||
"CreamChalkPouch",
|
|
||||||
"CrescentMoonBouquet",
|
|
||||||
"CrispleafAmaranthSachet",
|
|
||||||
"CutCoin",
|
"CutCoin",
|
||||||
"CypressMementoMori",
|
"CypressMementoMori",
|
||||||
"DevoutRavenWreath",
|
"DevoutRavenWreath",
|
||||||
@@ -24,6 +14,51 @@
|
|||||||
"DevoutSpottedowlWreath",
|
"DevoutSpottedowlWreath",
|
||||||
"DevoutTanagerWreath",
|
"DevoutTanagerWreath",
|
||||||
"EbonyMementoMori",
|
"EbonyMementoMori",
|
||||||
|
"GlassSplinter",
|
||||||
|
"HollowShell",
|
||||||
|
"IvoryMementoMori",
|
||||||
|
"MoldyOak",
|
||||||
|
"MuddySplinter",
|
||||||
|
"PutridOak",
|
||||||
|
"RavenWreath",
|
||||||
|
"RottenOak",
|
||||||
|
"ScratchedCoin",
|
||||||
|
"ShockSplinter",
|
||||||
|
"ShrikeWreath",
|
||||||
|
"ShroudofSeparation",
|
||||||
|
"SmokingSplinter",
|
||||||
|
"SpottedOwlWreath",
|
||||||
|
"SurvivorPudding",
|
||||||
|
"TanagerWreath"
|
||||||
|
],
|
||||||
|
"Campers": [
|
||||||
|
"Anniversary2019Offering",
|
||||||
|
"Anniversary2020Offering",
|
||||||
|
"Anniversary2021Offering",
|
||||||
|
"Anniversary2022Offering",
|
||||||
|
"Anniversary2023Offering",
|
||||||
|
"Anniversary2024Offering",
|
||||||
|
"Anniversary2025Offering",
|
||||||
|
"AnnotatedBlueprint",
|
||||||
|
"AzarovKey",
|
||||||
|
"BlackSaltStatuette",
|
||||||
|
"BloodiedBlueprint",
|
||||||
|
"BloodyPartyStreamers",
|
||||||
|
"BogLaurelSachet",
|
||||||
|
"BoundEnvelope",
|
||||||
|
"CattleTag28",
|
||||||
|
"ChalkPouch",
|
||||||
|
"CharredWeddingPhotograph",
|
||||||
|
"ChildrensBook",
|
||||||
|
"ClearReagent",
|
||||||
|
"ColdwindCattleTag81",
|
||||||
|
"CreamChalkPouch",
|
||||||
|
"CrescentMoonBouquet",
|
||||||
|
"CrispleafAmaranthSachet",
|
||||||
|
"DecrepitClapboard",
|
||||||
|
"EclipseThemeOffering",
|
||||||
|
"EmergencyCertificate",
|
||||||
|
"EscapeCake",
|
||||||
"FaintReagent",
|
"FaintReagent",
|
||||||
"FragrantBogLaurel",
|
"FragrantBogLaurel",
|
||||||
"FragrantCrispleafAmaranth",
|
"FragrantCrispleafAmaranth",
|
||||||
@@ -34,93 +69,62 @@
|
|||||||
"FreshPrimroseBlossom",
|
"FreshPrimroseBlossom",
|
||||||
"FreshSweetWilliam",
|
"FreshSweetWilliam",
|
||||||
"FullMoonBouquet",
|
"FullMoonBouquet",
|
||||||
"HazyReagent",
|
|
||||||
"HeartLocket",
|
|
||||||
"HollowShell",
|
|
||||||
"IvoryChalkPouch",
|
|
||||||
"IvoryMementoMori",
|
|
||||||
"LunacyTicket",
|
|
||||||
"MacmillanLedgerPage",
|
|
||||||
"MacMillanPhalanxBone",
|
|
||||||
"MoldyOak",
|
|
||||||
"MurkyReagent",
|
|
||||||
"NewMoonBouquet",
|
|
||||||
"PElliotLunacyTicket",
|
|
||||||
"PetrifiedOak",
|
|
||||||
"PrimroseBlossomSachet",
|
|
||||||
"PutridOak",
|
|
||||||
"QuarterMoonBouquet",
|
|
||||||
"RavenWreath",
|
|
||||||
"RottenOak",
|
|
||||||
"SaltPouch",
|
|
||||||
"ScratchedCoin",
|
|
||||||
"SealedEnvelope",
|
|
||||||
"ShinyCoin",
|
|
||||||
"ShreddedPlate",
|
|
||||||
"ShrikeWreath",
|
|
||||||
"ShroudofBinding",
|
|
||||||
"ShroudofSeparation",
|
|
||||||
"ShroudofUnion",
|
|
||||||
"SignedLedgerPage",
|
|
||||||
"SpottedOwlWreath",
|
|
||||||
"SweetWilliamSachet",
|
|
||||||
"TanagerWreath",
|
|
||||||
"TarnishedCoin",
|
|
||||||
"VigosShroud",
|
|
||||||
"VirginiaPlate",
|
|
||||||
"WhiteWard",
|
|
||||||
"VigosJarOfSaltyLips",
|
|
||||||
"MuddySplinter",
|
|
||||||
"FumingCordage",
|
"FumingCordage",
|
||||||
"FumingWelcomeSign",
|
"FumingWelcomeSign",
|
||||||
"GranmasCookbook",
|
"GranmasCookbook",
|
||||||
"EscapeCake",
|
|
||||||
"SurvivorPudding",
|
|
||||||
"BloodyPartyStreamers",
|
|
||||||
"Anniversary2019Offering",
|
|
||||||
"Anniversary2022Offering",
|
|
||||||
"Anniversary2024Offering",
|
|
||||||
"Winter2024Offering",
|
|
||||||
"Spring2024Offering",
|
|
||||||
"Anniversary2025Offering",
|
|
||||||
"Anniversary2023Offering",
|
|
||||||
"Halloween2022Offering",
|
|
||||||
"Halloween2021Offering",
|
"Halloween2021Offering",
|
||||||
"PaintedRiverRock",
|
"Halloween2022Offering",
|
||||||
"ChildrensBook",
|
|
||||||
"TheLastMask",
|
|
||||||
"Anniversary2021Offering",
|
|
||||||
"SummerOffering",
|
|
||||||
"QatarThemeOffering",
|
|
||||||
"RedMoney",
|
|
||||||
"SmokingSplinter",
|
|
||||||
"ThePiedPiper",
|
|
||||||
"QuicheThemeOffering",
|
|
||||||
"WormholeThemeOffering",
|
|
||||||
"Anniversary2020Offering",
|
|
||||||
"BlackSplinter",
|
|
||||||
"StrodeRealtyKey",
|
|
||||||
"DecrepitClapboard",
|
|
||||||
"HarvestFestivalLeaflet",
|
|
||||||
"ShockSplinter",
|
|
||||||
"EmergencyCertificate",
|
|
||||||
"PsychiatricAssessmentReport",
|
|
||||||
"ShatteredGlasses",
|
|
||||||
"HalloweenOffering",
|
"HalloweenOffering",
|
||||||
"UmbraThemeOffering",
|
"HarvestFestivalLeaflet",
|
||||||
"QuantumThemeOffering",
|
"HazyReagent",
|
||||||
"KenyaThemeOffering",
|
"HeartLocket",
|
||||||
"EclipseThemeOffering",
|
|
||||||
"IonThemeOffering",
|
"IonThemeOffering",
|
||||||
"GlassSplinter",
|
"IvoryChalkPouch",
|
||||||
"Jigsawpiece",
|
|
||||||
"JapaneseCountrySide",
|
"JapaneseCountrySide",
|
||||||
|
"Jigsawpiece",
|
||||||
|
"KenyaThemeOffering",
|
||||||
|
"LunacyTicket",
|
||||||
|
"MacmillanLedgerPage",
|
||||||
|
"MacMillanPhalanxBone",
|
||||||
"MeteorThemeOffering",
|
"MeteorThemeOffering",
|
||||||
"UkraineThemeOffering",
|
"MurkyReagent",
|
||||||
|
"NewMoonBouquet",
|
||||||
|
"PaintedRiverRock",
|
||||||
|
"PElliotLunacyTicket",
|
||||||
|
"PetrifiedOak",
|
||||||
|
"PrimroseBlossomSachet",
|
||||||
|
"PsychiatricAssessmentReport",
|
||||||
|
"QatarThemeOffering",
|
||||||
|
"QuantumThemeOffering",
|
||||||
|
"QuarterMoonBouquet",
|
||||||
|
"QuicheThemeOffering",
|
||||||
|
"RedMoney",
|
||||||
"SacrificalWard",
|
"SacrificalWard",
|
||||||
|
"SaltPouch",
|
||||||
|
"SealedEnvelope",
|
||||||
|
"ShatteredGlasses",
|
||||||
|
"ShinyCoin",
|
||||||
|
"ShreddedPlate",
|
||||||
|
"ShroudofBinding",
|
||||||
|
"ShroudofUnion",
|
||||||
|
"SignedLedgerPage",
|
||||||
|
"Spring2024Offering",
|
||||||
|
"StrodeRealtyKey",
|
||||||
|
"SummerOffering",
|
||||||
|
"SweetWilliamSachet",
|
||||||
|
"TarnishedCoin",
|
||||||
|
"TheLastMask",
|
||||||
|
"ThePiedPiper",
|
||||||
"TornBlueprint",
|
"TornBlueprint",
|
||||||
"BloodiedBlueprint",
|
"UkraineThemeOffering",
|
||||||
|
"UmbraThemeOffering",
|
||||||
"VigosBlueprint",
|
"VigosBlueprint",
|
||||||
"AnnotatedBlueprint",
|
"VigosJarOfSaltyLips",
|
||||||
"WalesThemeOffering"
|
"VigosShroud",
|
||||||
|
"VirginiaPlate",
|
||||||
|
"WalesThemeOffering",
|
||||||
|
"WhiteWard",
|
||||||
|
"Winter2024Offering",
|
||||||
|
"WormholeThemeOffering"
|
||||||
]
|
]
|
||||||
|
}
|
||||||
+287
-283
@@ -1,313 +1,317 @@
|
|||||||
[
|
{
|
||||||
"S45P01",
|
"Slashers": [
|
||||||
"S45P02",
|
"Agitation",
|
||||||
"S45P03",
|
"Bamboozle",
|
||||||
"K38P01",
|
|
||||||
"K38P02",
|
|
||||||
"K38P03",
|
|
||||||
"K37P01",
|
|
||||||
"K37P02",
|
|
||||||
"K37P03",
|
|
||||||
"TacticianBlastMine_Calamari",
|
|
||||||
"TacticianWireTap_Calamari",
|
|
||||||
"BetterTogether",
|
|
||||||
"Fixated",
|
|
||||||
"InnerStrength",
|
|
||||||
"S24P01",
|
|
||||||
"S24P02",
|
|
||||||
"S24P03",
|
|
||||||
"S31P01",
|
|
||||||
"S31P02",
|
|
||||||
"S31P03",
|
|
||||||
"S32P01",
|
|
||||||
"S32P02",
|
|
||||||
"S32P03",
|
|
||||||
"Solidarity",
|
|
||||||
"Poised",
|
|
||||||
"HeadOn",
|
|
||||||
"S29P01",
|
|
||||||
"S29P02",
|
|
||||||
"S29P03",
|
|
||||||
"S27P01",
|
|
||||||
"S27P02",
|
|
||||||
"S27P03",
|
|
||||||
"LuckyBreak",
|
|
||||||
"AnyMeansNecessary",
|
|
||||||
"Breakout",
|
|
||||||
"DeadHard",
|
|
||||||
"NoMither",
|
|
||||||
"WereGonnaLiveForever",
|
|
||||||
"S26P01",
|
|
||||||
"S26P02",
|
|
||||||
"S26P03",
|
|
||||||
"S35P01",
|
|
||||||
"S35P02",
|
|
||||||
"S35P03",
|
|
||||||
"S39P01",
|
|
||||||
"S39P02",
|
|
||||||
"S39P03",
|
|
||||||
"FlipFlop",
|
|
||||||
"BuckleUp",
|
|
||||||
"TheMettleOfMan",
|
|
||||||
"Babysitter",
|
|
||||||
"Camaraderie",
|
|
||||||
"SecondWind",
|
|
||||||
"K27P01",
|
|
||||||
"K27P02",
|
|
||||||
"K27P03",
|
|
||||||
"Ace_In_The_Hole",
|
|
||||||
"Open_Handed",
|
|
||||||
"Up_The_Ante",
|
|
||||||
"Thanatophobia",
|
|
||||||
"NurseCalling",
|
|
||||||
"Stridor",
|
|
||||||
"K33P01",
|
|
||||||
"K33P02",
|
|
||||||
"K33P03",
|
|
||||||
"WindowsOfOpportunity",
|
|
||||||
"BoilOver",
|
|
||||||
"Dance_with_me",
|
|
||||||
"OverwhelmingPresence",
|
|
||||||
"MonitorAndAbuse",
|
|
||||||
"GeneratorOvercharge",
|
|
||||||
"K29P01",
|
|
||||||
"K29P02",
|
|
||||||
"K29P03",
|
|
||||||
"Adrenaline",
|
|
||||||
"QuickQuiet",
|
|
||||||
"Sprint_Burst",
|
|
||||||
"Balanced_Landing",
|
|
||||||
"Urban_Evasion",
|
|
||||||
"Streetwise",
|
|
||||||
"S34P01",
|
|
||||||
"S34P02",
|
|
||||||
"S34P03",
|
|
||||||
"S33P01",
|
|
||||||
"S33P02",
|
|
||||||
"S33P03",
|
|
||||||
"S25P01",
|
|
||||||
"S25P02",
|
|
||||||
"S25P03",
|
|
||||||
"Breakdown",
|
|
||||||
"Aftercare",
|
|
||||||
"Distortion",
|
|
||||||
"BBQAndChilli",
|
"BBQAndChilli",
|
||||||
"InTheDark",
|
|
||||||
"FranklinsLoss",
|
|
||||||
"HangmansTrick",
|
|
||||||
"Surveillance",
|
|
||||||
"MakeYourChoice",
|
|
||||||
"BorrowedTime",
|
|
||||||
"SelfSufficient",
|
|
||||||
"LeftBehind",
|
|
||||||
"BeastOfPrey",
|
"BeastOfPrey",
|
||||||
"Hex_HuntressLullaby",
|
"Bitter_Murmur",
|
||||||
"TerritorialImperative",
|
"BloodEcho",
|
||||||
"Botany_Knowledge",
|
|
||||||
"Empathy",
|
|
||||||
"Self_Care",
|
|
||||||
"OffTheRecord",
|
|
||||||
"RedHerring",
|
|
||||||
"ForThePeople",
|
|
||||||
"K24P01",
|
|
||||||
"K24P02",
|
|
||||||
"K24P03",
|
|
||||||
"S38P01",
|
|
||||||
"S38P02",
|
|
||||||
"S38P03",
|
|
||||||
"BloodHound",
|
"BloodHound",
|
||||||
"Predator",
|
"BloodWarden",
|
||||||
"Shadowborn",
|
"BoonDestroyer",
|
||||||
"ImAllEars",
|
"Brutal_Strength",
|
||||||
"ThrillingTremors",
|
"CorruptIntervention",
|
||||||
|
"Coulrophobia",
|
||||||
|
"CruelConfinement",
|
||||||
|
"DarkDevotion",
|
||||||
|
"DeadMansSwitch",
|
||||||
|
"Deathbound",
|
||||||
|
"Deerstalker",
|
||||||
|
"Discordance",
|
||||||
|
"Distressing",
|
||||||
|
"DragonsGrip",
|
||||||
|
"Dying_Light",
|
||||||
|
"Enduring",
|
||||||
|
"FireUp",
|
||||||
|
"ForcedPenance",
|
||||||
|
"FranklinsLoss",
|
||||||
"FurtiveChase",
|
"FurtiveChase",
|
||||||
"S41P01",
|
"Gearhead",
|
||||||
"S41P02",
|
"GeneratorOvercharge",
|
||||||
"S41P03",
|
"HangmansTrick",
|
||||||
"SoleSurvivor",
|
"Hex_Devour_Hope",
|
||||||
"DecisiveStrike",
|
"Hex_HauntedGround",
|
||||||
"ObjectOfObsession",
|
"Hex_HuntressLullaby",
|
||||||
"K34P01",
|
"Hex_Ruin",
|
||||||
"K34P02",
|
"Hex_The_Third_Seal",
|
||||||
"K34P03",
|
"Hex_Thrill_Of_The_Hunt",
|
||||||
|
"HexBloodFavor",
|
||||||
|
"HexRetribution",
|
||||||
|
"HexUndying",
|
||||||
|
"ImAllEars",
|
||||||
|
"InfectiousFright",
|
||||||
|
"Insidious",
|
||||||
|
"InTheDark",
|
||||||
|
"Iron_Grasp",
|
||||||
|
"Ironmaiden",
|
||||||
|
"K22P01",
|
||||||
|
"K22P02",
|
||||||
|
"K22P03",
|
||||||
"K23P01",
|
"K23P01",
|
||||||
"K23P02",
|
"K23P02",
|
||||||
"K23P03",
|
"K23P03",
|
||||||
"K30P01",
|
"K24P01",
|
||||||
"K30P02",
|
"K24P02",
|
||||||
"K30P03",
|
"K24P03",
|
||||||
"Visionary",
|
|
||||||
"DesperateMeasures",
|
|
||||||
"BuiltToLast",
|
|
||||||
"Bond",
|
|
||||||
"Leader",
|
|
||||||
"Prove_Thyself",
|
|
||||||
"CorruptIntervention",
|
|
||||||
"InfectiousFright",
|
|
||||||
"DarkDevotion",
|
|
||||||
"S49P01",
|
|
||||||
"S49P02",
|
|
||||||
"S49P03",
|
|
||||||
"K41P01",
|
|
||||||
"K41P02",
|
|
||||||
"K41P03",
|
|
||||||
"SoulGuard",
|
|
||||||
"BloodPact",
|
|
||||||
"RepressedAlliance",
|
|
||||||
"Discordance",
|
|
||||||
"MadGrit",
|
|
||||||
"Ironmaiden",
|
|
||||||
"S30P01",
|
|
||||||
"S30P02",
|
|
||||||
"S30P03",
|
|
||||||
"K42P01",
|
|
||||||
"K42P02",
|
|
||||||
"K42P03",
|
|
||||||
"K35P01",
|
|
||||||
"K35P02",
|
|
||||||
"K35P03",
|
|
||||||
"S46P01",
|
|
||||||
"S46P02",
|
|
||||||
"S46P03",
|
|
||||||
"S28P01",
|
|
||||||
"S28P02",
|
|
||||||
"S28P03",
|
|
||||||
"ZanshinTactics",
|
|
||||||
"BloodEcho",
|
|
||||||
"Nemesis",
|
|
||||||
"S42P01",
|
|
||||||
"S42P02",
|
|
||||||
"S42P03",
|
|
||||||
"S51P01",
|
|
||||||
"S51P02",
|
|
||||||
"S51P03",
|
|
||||||
"S50P01",
|
|
||||||
"S50P02",
|
|
||||||
"S50P03",
|
|
||||||
"Calm_Spirit",
|
|
||||||
"Iron_Will",
|
|
||||||
"Saboteur",
|
|
||||||
"K32P01",
|
|
||||||
"K32P02",
|
|
||||||
"K32P03",
|
|
||||||
"S52P01",
|
|
||||||
"S52P02",
|
|
||||||
"S52P03",
|
|
||||||
"BloodWarden",
|
|
||||||
"FireUp",
|
|
||||||
"RememberMe",
|
|
||||||
"ForcedPenance",
|
|
||||||
"TrailofTorment",
|
|
||||||
"Deathbound",
|
|
||||||
"S37P01",
|
|
||||||
"S37P02",
|
|
||||||
"S37P03",
|
|
||||||
"S47P01",
|
|
||||||
"S47P02",
|
|
||||||
"S47P03",
|
|
||||||
"Save_The_Best_For_Last",
|
|
||||||
"Dying_Light",
|
|
||||||
"Play_With_Your_Food",
|
|
||||||
"S48P01",
|
|
||||||
"S48P02",
|
|
||||||
"S48P03",
|
|
||||||
"Technician",
|
|
||||||
"Lithe",
|
|
||||||
"Alert",
|
|
||||||
"Pharmacy",
|
|
||||||
"Vigil",
|
|
||||||
"WakeUp",
|
|
||||||
"Diversion",
|
|
||||||
"Deliverance",
|
|
||||||
"Autodidact",
|
|
||||||
"S40P01",
|
|
||||||
"S40P02",
|
|
||||||
"S40P03",
|
|
||||||
"S36P01",
|
|
||||||
"S36P02",
|
|
||||||
"S36P03",
|
|
||||||
"K25P01",
|
"K25P01",
|
||||||
"K25P02",
|
"K25P02",
|
||||||
"K25P03",
|
"K25P03",
|
||||||
"K31P01",
|
"K26P01",
|
||||||
"K31P02",
|
"K26P02",
|
||||||
"K31P03",
|
"K26P03",
|
||||||
"S43P01",
|
"K27P01",
|
||||||
"S43P02",
|
"K27P02",
|
||||||
"S43P03",
|
"K27P03",
|
||||||
"K28P01",
|
"K28P01",
|
||||||
"K28P02",
|
"K28P02",
|
||||||
"K28P03",
|
"K28P03",
|
||||||
|
"K29P01",
|
||||||
|
"K29P02",
|
||||||
|
"K29P03",
|
||||||
|
"K30P01",
|
||||||
|
"K30P02",
|
||||||
|
"K30P03",
|
||||||
|
"K31P01",
|
||||||
|
"K31P02",
|
||||||
|
"K31P03",
|
||||||
|
"K32P01",
|
||||||
|
"K32P02",
|
||||||
|
"K32P03",
|
||||||
|
"K33P01",
|
||||||
|
"K33P02",
|
||||||
|
"K33P03",
|
||||||
|
"K34P01",
|
||||||
|
"K34P02",
|
||||||
|
"K34P03",
|
||||||
|
"K35P01",
|
||||||
|
"K35P02",
|
||||||
|
"K35P03",
|
||||||
|
"K36P01",
|
||||||
|
"K36P02",
|
||||||
|
"K36P03",
|
||||||
|
"K37P01",
|
||||||
|
"K37P02",
|
||||||
|
"K37P03",
|
||||||
|
"K38P01",
|
||||||
|
"K38P02",
|
||||||
|
"K38P03",
|
||||||
"K39P01",
|
"K39P01",
|
||||||
"K39P02",
|
"K39P02",
|
||||||
"K39P03",
|
"K39P03",
|
||||||
"K40P01",
|
"K40P01",
|
||||||
"K40P02",
|
"K40P02",
|
||||||
"K40P03",
|
"K40P03",
|
||||||
"Surge",
|
"K41P01",
|
||||||
|
"K41P02",
|
||||||
|
"K41P03",
|
||||||
|
"K42P01",
|
||||||
|
"K42P02",
|
||||||
|
"K42P03",
|
||||||
|
"Lightborn",
|
||||||
|
"MadGrit",
|
||||||
|
"MakeYourChoice",
|
||||||
"Mindbreaker",
|
"Mindbreaker",
|
||||||
"CruelConfinement",
|
"MonitorAndAbuse",
|
||||||
"Tenacity",
|
"Monstrous_Shrine",
|
||||||
|
"Nemesis",
|
||||||
|
"No_One_Escapes_Death",
|
||||||
|
"NurseCalling",
|
||||||
|
"OverwhelmingPresence",
|
||||||
|
"Play_With_Your_Food",
|
||||||
|
"pop_goes_the_weasel",
|
||||||
|
"Predator",
|
||||||
|
"Rancor",
|
||||||
|
"RememberMe",
|
||||||
|
"Save_The_Best_For_Last",
|
||||||
|
"Shadowborn",
|
||||||
|
"Sloppy_Butcher",
|
||||||
|
"Spies_From_The_Shadows",
|
||||||
|
"SpiritFury",
|
||||||
|
"Stridor",
|
||||||
|
"Surge",
|
||||||
|
"Surveillance",
|
||||||
|
"TerritorialImperative",
|
||||||
|
"Thanatophobia",
|
||||||
|
"ThrillingTremors",
|
||||||
|
"Tinkerer",
|
||||||
|
"TrailofTorment",
|
||||||
|
"Unnerving_Presence",
|
||||||
|
"Unrelenting",
|
||||||
|
"Whispers",
|
||||||
|
"ZanshinTactics"
|
||||||
|
],
|
||||||
|
"Campers": [
|
||||||
|
"Ace_In_The_Hole",
|
||||||
|
"Adrenaline",
|
||||||
|
"Aftercare",
|
||||||
|
"Alert",
|
||||||
|
"AnyMeansNecessary",
|
||||||
|
"Autodidact",
|
||||||
|
"Babysitter",
|
||||||
|
"Balanced_Landing",
|
||||||
|
"BetterTogether",
|
||||||
|
"BloodPact",
|
||||||
|
"BoilOver",
|
||||||
|
"Bond",
|
||||||
|
"BorrowedTime",
|
||||||
|
"Botany_Knowledge",
|
||||||
|
"Breakdown",
|
||||||
|
"Breakout",
|
||||||
|
"BuckleUp",
|
||||||
|
"BuiltToLast",
|
||||||
|
"Calm_Spirit",
|
||||||
|
"Camaraderie",
|
||||||
|
"Dance_with_me",
|
||||||
|
"Dark_Sense",
|
||||||
|
"DeadHard",
|
||||||
|
"DecisiveStrike",
|
||||||
|
"Deja_Vu",
|
||||||
|
"Deliverance",
|
||||||
|
"DesperateMeasures",
|
||||||
"DetectivesHunch",
|
"DetectivesHunch",
|
||||||
"StakeOut",
|
"Distortion",
|
||||||
"K36P01",
|
"Diversion",
|
||||||
"K36P02",
|
"Empathy",
|
||||||
"K36P03",
|
"Fixated",
|
||||||
|
"FlipFlop",
|
||||||
|
"ForThePeople",
|
||||||
|
"HeadOn",
|
||||||
|
"Hope",
|
||||||
|
"InnerStrength",
|
||||||
|
"Iron_Will",
|
||||||
|
"Kindred",
|
||||||
|
"Leader",
|
||||||
|
"LeftBehind",
|
||||||
|
"Lightweight",
|
||||||
|
"Lithe",
|
||||||
|
"LuckyBreak",
|
||||||
|
"No_One_Left_Behind",
|
||||||
|
"NoMither",
|
||||||
|
"ObjectOfObsession",
|
||||||
|
"OffTheRecord",
|
||||||
|
"Open_Handed",
|
||||||
|
"Pharmacy",
|
||||||
|
"Plunderers_Instinct",
|
||||||
|
"Poised",
|
||||||
|
"Premonition",
|
||||||
|
"Prove_Thyself",
|
||||||
|
"QuickQuiet",
|
||||||
|
"RedHerring",
|
||||||
|
"RepressedAlliance",
|
||||||
|
"Resilience",
|
||||||
|
"S24P01",
|
||||||
|
"S24P02",
|
||||||
|
"S24P03",
|
||||||
|
"S25P01",
|
||||||
|
"S25P02",
|
||||||
|
"S25P03",
|
||||||
|
"S26P01",
|
||||||
|
"S26P02",
|
||||||
|
"S26P03",
|
||||||
|
"S27P01",
|
||||||
|
"S27P02",
|
||||||
|
"S27P03",
|
||||||
|
"S28P01",
|
||||||
|
"S28P02",
|
||||||
|
"S28P03",
|
||||||
|
"S29P01",
|
||||||
|
"S29P02",
|
||||||
|
"S29P03",
|
||||||
|
"S30P01",
|
||||||
|
"S30P02",
|
||||||
|
"S30P03",
|
||||||
|
"S31P01",
|
||||||
|
"S31P02",
|
||||||
|
"S31P03",
|
||||||
|
"S32P01",
|
||||||
|
"S32P02",
|
||||||
|
"S32P03",
|
||||||
|
"S33P01",
|
||||||
|
"S33P02",
|
||||||
|
"S33P03",
|
||||||
|
"S34P01",
|
||||||
|
"S34P02",
|
||||||
|
"S34P03",
|
||||||
|
"S35P01",
|
||||||
|
"S35P02",
|
||||||
|
"S35P03",
|
||||||
|
"S36P01",
|
||||||
|
"S36P02",
|
||||||
|
"S36P03",
|
||||||
|
"S37P01",
|
||||||
|
"S37P02",
|
||||||
|
"S37P03",
|
||||||
|
"S38P01",
|
||||||
|
"S38P02",
|
||||||
|
"S38P03",
|
||||||
|
"S39P01",
|
||||||
|
"S39P02",
|
||||||
|
"S39P03",
|
||||||
|
"S40P01",
|
||||||
|
"S40P02",
|
||||||
|
"S40P03",
|
||||||
|
"S41P01",
|
||||||
|
"S41P02",
|
||||||
|
"S41P03",
|
||||||
|
"S42P01",
|
||||||
|
"S42P02",
|
||||||
|
"S42P03",
|
||||||
|
"S43P01",
|
||||||
|
"S43P02",
|
||||||
|
"S43P03",
|
||||||
"S44P01",
|
"S44P01",
|
||||||
"S44P02",
|
"S44P02",
|
||||||
"S44P03",
|
"S44P03",
|
||||||
"Agitation",
|
"S45P01",
|
||||||
"Bitter_Murmur",
|
"S45P02",
|
||||||
"Brutal_Strength",
|
"S45P03",
|
||||||
"Dark_Sense",
|
"S46P01",
|
||||||
"Deerstalker",
|
"S46P02",
|
||||||
"Deja_Vu",
|
"S46P03",
|
||||||
"Distressing",
|
"S47P01",
|
||||||
"Enduring",
|
"S47P02",
|
||||||
"Hex_Devour_Hope",
|
"S47P03",
|
||||||
"Hex_Ruin",
|
"S48P01",
|
||||||
"Hex_The_Third_Seal",
|
"S48P02",
|
||||||
"Hex_Thrill_Of_The_Hunt",
|
"S48P03",
|
||||||
"Hope",
|
"S49P01",
|
||||||
"Insidious",
|
"S49P02",
|
||||||
"Iron_Grasp",
|
"S49P03",
|
||||||
"Kindred",
|
"S50P01",
|
||||||
"Lightborn",
|
"S50P02",
|
||||||
"Lightweight",
|
"S50P03",
|
||||||
"Monstrous_Shrine",
|
"S51P01",
|
||||||
"No_One_Escapes_Death",
|
"S51P02",
|
||||||
"No_One_Left_Behind",
|
"S51P03",
|
||||||
"Plunderers_Instinct",
|
"S52P01",
|
||||||
"Premonition",
|
"S52P02",
|
||||||
"Resilience",
|
"S52P03",
|
||||||
|
"Saboteur",
|
||||||
|
"SecondWind",
|
||||||
|
"Self_Care",
|
||||||
|
"SelfSufficient",
|
||||||
"Slippery_Meat",
|
"Slippery_Meat",
|
||||||
"Sloppy_Butcher",
|
|
||||||
"Small_Game",
|
"Small_Game",
|
||||||
"Spies_From_The_Shadows",
|
"SoleSurvivor",
|
||||||
|
"Solidarity",
|
||||||
|
"SoulGuard",
|
||||||
"Spine_Chill",
|
"Spine_Chill",
|
||||||
|
"Sprint_Burst",
|
||||||
|
"StakeOut",
|
||||||
|
"Streetwise",
|
||||||
|
"TacticianBlastMine_Calamari",
|
||||||
|
"TacticianWireTap_Calamari",
|
||||||
|
"Technician",
|
||||||
|
"Tenacity",
|
||||||
|
"TheMettleOfMan",
|
||||||
"This_Is_Not_Happening",
|
"This_Is_Not_Happening",
|
||||||
"Tinkerer",
|
"Up_The_Ante",
|
||||||
"Unnerving_Presence",
|
"Urban_Evasion",
|
||||||
"Unrelenting",
|
"Vigil",
|
||||||
|
"Visionary",
|
||||||
|
"WakeUp",
|
||||||
"WellMakeIt",
|
"WellMakeIt",
|
||||||
"Whispers",
|
"WereGonnaLiveForever",
|
||||||
"K22P01",
|
"WindowsOfOpportunity"
|
||||||
"K22P02",
|
|
||||||
"K22P03",
|
|
||||||
"K26P01",
|
|
||||||
"K26P02",
|
|
||||||
"K26P03",
|
|
||||||
"SpiritFury",
|
|
||||||
"Hex_HauntedGround",
|
|
||||||
"Rancor",
|
|
||||||
"BoonDestroyer",
|
|
||||||
"Gearhead",
|
|
||||||
"DeadMansSwitch",
|
|
||||||
"HexRetribution",
|
|
||||||
"Coulrophobia",
|
|
||||||
"pop_goes_the_weasel",
|
|
||||||
"Bamboozle",
|
|
||||||
"DragonsGrip",
|
|
||||||
"HexUndying",
|
|
||||||
"HexBloodFavor"
|
|
||||||
]
|
]
|
||||||
|
}
|
||||||
+75
-35
@@ -2,6 +2,7 @@ using CUE4Parse.Compression;
|
|||||||
using CUE4Parse.Encryption.Aes;
|
using CUE4Parse.Encryption.Aes;
|
||||||
using CUE4Parse.FileProvider;
|
using CUE4Parse.FileProvider;
|
||||||
using CUE4Parse.MappingsProvider;
|
using CUE4Parse.MappingsProvider;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.Actor;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse.UE4.Assets.Objects.Properties;
|
using CUE4Parse.UE4.Assets.Objects.Properties;
|
||||||
@@ -12,9 +13,9 @@ using Newtonsoft.Json;
|
|||||||
|
|
||||||
class DumpByDaylight
|
class DumpByDaylight
|
||||||
{
|
{
|
||||||
private const string _pakDir = "E:\\Program Files (x86)\\Steam\\steamapps\\common\\Dead by Daylight\\DeadByDaylight\\Content\\Paks";
|
private const string _pakDir = "D:\\XboxGames\\Dead By Daylight\\Content\\DeadByDaylight\\Content\\Paks";
|
||||||
private const string _aesKey = "0x22B1639B548124925CF7B9CBAA09F9AC295FCF0324586D6B37EE1D42670B39B3";
|
private const string _aesKey = "0x22B1639B548124925CF7B9CBAA09F9AC295FCF0324586D6B37EE1D42670B39B3";
|
||||||
private const string _mappingURL = "https://github.com/Masusder/FModel-DbdMappings/raw/refs/heads/main/Mappings/9.5.0/5.4.4-3172922+++DeadByDaylight+Quiche_REL-DeadByDaylight.usmap";
|
private const string _mappingURL = "https://git.neru.rip/neru/UnlockedByDaylight/raw/branch/main/res/mappings/latest-xbox.usmap";
|
||||||
|
|
||||||
public static async Task<string?> DownloadMappingFileAsync(string url, string savePath)
|
public static async Task<string?> DownloadMappingFileAsync(string url, string savePath)
|
||||||
{
|
{
|
||||||
@@ -66,33 +67,36 @@ class DumpByDaylight
|
|||||||
|
|
||||||
Console.WriteLine("\nProvider Initialized. Extracting Databases...");
|
Console.WriteLine("\nProvider Initialized. Extracting Databases...");
|
||||||
|
|
||||||
var dataPak = provider.GetArchive("pakchunk4-Windows.utoc");
|
var dataPak = provider.GetArchive("pakchunk4-WinGDK.utoc");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* itemdb dump
|
* itemdb dump
|
||||||
*/
|
*/
|
||||||
var searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/ItemDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
var searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/ItemDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
var items = new List<string>();
|
var camperItems = new List<string>();
|
||||||
var powers = new List<string>();
|
|
||||||
|
var slasherPowers = new List<string>();
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
foreach (var path in searchPaths)
|
||||||
{
|
{
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"getting items / powers from {Path.GetFileName(path)}");
|
Console.WriteLine($"getting items / powers from {Path.GetFullPath(path)}");
|
||||||
|
|
||||||
foreach (var row in dataTable.RowMap)
|
foreach (var row in dataTable.RowMap)
|
||||||
{
|
{
|
||||||
|
var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role");
|
||||||
var typeProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Type");
|
var typeProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Type");
|
||||||
if (typeProperty?.Tag is EnumProperty enumProp)
|
if (typeProperty?.Tag is EnumProperty typeProp && roleProperty?.Tag is EnumProperty roleProp)
|
||||||
{
|
{
|
||||||
string typeName = enumProp.Value.ToString() ?? "null";
|
string typeName = typeProp.Value.ToString() ?? "null";
|
||||||
|
string roleName = roleProp.Value.ToString() ?? "null";
|
||||||
|
|
||||||
if (typeName == "EInventoryItemType::Power")
|
if (roleName == "EPlayerRole::VE_Slasher")
|
||||||
powers.Add(row.Key.Text);
|
slasherPowers.Add(row.Key.Text);
|
||||||
else if (typeName == "EInventoryItemType::Item")
|
else
|
||||||
items.Add(row.Key.Text);
|
camperItems.Add(row.Key.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,8 +104,8 @@ class DumpByDaylight
|
|||||||
|
|
||||||
var itemsSerialized = new
|
var itemsSerialized = new
|
||||||
{
|
{
|
||||||
Items = items.OrderBy(x => x).ToList(),
|
Camper = new { Items = camperItems.OrderBy(x => x).ToList() },
|
||||||
Powers = powers.OrderBy(x => x).ToList()
|
Slasher = new { Powers = slasherPowers.OrderBy(x => x).ToList() }
|
||||||
};
|
};
|
||||||
File.WriteAllText("items.json", JsonConvert.SerializeObject(itemsSerialized, Formatting.Indented));
|
File.WriteAllText("items.json", JsonConvert.SerializeObject(itemsSerialized, Formatting.Indented));
|
||||||
|
|
||||||
@@ -109,36 +113,39 @@ class DumpByDaylight
|
|||||||
* addon dump
|
* addon dump
|
||||||
*/
|
*/
|
||||||
searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/ItemAddonDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/ItemAddonDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
var itemAddons = new List<string>();
|
var camperAddons = new List<string>();
|
||||||
var powerAddons = new List<string>();
|
var slasherAddons = new List<string>();
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
foreach (var path in searchPaths)
|
||||||
{
|
{
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"getting item / power addons from {Path.GetFileName(path)}");
|
Console.WriteLine($"getting item / power addons from {Path.GetFullPath(path)}");
|
||||||
|
|
||||||
foreach (var row in dataTable.RowMap)
|
foreach (var row in dataTable.RowMap)
|
||||||
{
|
{
|
||||||
var typeProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Type");
|
bool isSlasherAddon = false;
|
||||||
if (typeProperty?.Tag is EnumProperty enumProp)
|
var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role");
|
||||||
|
if (roleProperty?.Tag is EnumProperty roleEnumProp)
|
||||||
{
|
{
|
||||||
string typeName = enumProp.Value.ToString() ?? "null";
|
string roleName = roleEnumProp.Value.ToString() ?? "null";
|
||||||
|
if (roleName == "EPlayerRole::VE_Slasher")
|
||||||
if (typeName == "EInventoryItemType::ItemAddOn")
|
isSlasherAddon = true;
|
||||||
itemAddons.Add(row.Key.Text);
|
|
||||||
else if (typeName == "EInventoryItemType::PowerAddOn")
|
|
||||||
powerAddons.Add(row.Key.Text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSlasherAddon)
|
||||||
|
slasherAddons.Add(row.Key.Text);
|
||||||
|
else
|
||||||
|
camperAddons.Add(row.Key.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var addonsSerialized = new
|
var addonsSerialized = new
|
||||||
{
|
{
|
||||||
Items = itemAddons.OrderBy(x => x).ToList(),
|
Slashers = slasherAddons.OrderBy(x => x).ToList(),
|
||||||
Powers = powerAddons.OrderBy(x => x).ToList()
|
Campers = camperAddons.OrderBy(x => x).ToList()
|
||||||
};
|
};
|
||||||
File.WriteAllText("addons.json", JsonConvert.SerializeObject(addonsSerialized, Formatting.Indented));
|
File.WriteAllText("addons.json", JsonConvert.SerializeObject(addonsSerialized, Formatting.Indented));
|
||||||
|
|
||||||
@@ -147,40 +154,73 @@ class DumpByDaylight
|
|||||||
*/
|
*/
|
||||||
searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/OfferingDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/OfferingDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
|
||||||
var offerings = new List<string>();
|
var camperOfferings = new List<string>();
|
||||||
|
var slasherOfferings = new List<string>();
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
foreach (var path in searchPaths)
|
||||||
{
|
{
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"getting offerings from {Path.GetFileName(path)}");
|
Console.WriteLine($"getting offerings from {Path.GetFullPath(path)}");
|
||||||
|
|
||||||
foreach (var row in dataTable.RowMap)
|
foreach (var row in dataTable.RowMap)
|
||||||
offerings.Add(row.Key.Text);
|
{
|
||||||
|
var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role");
|
||||||
|
if (roleProperty?.Tag is EnumProperty roleEnumProp)
|
||||||
|
{
|
||||||
|
string roleName = roleEnumProp.Value.ToString() ?? "null";
|
||||||
|
if (roleName == "EPlayerRole::VE_Slasher")
|
||||||
|
slasherOfferings.Add(row.Key.Text);
|
||||||
|
else
|
||||||
|
camperOfferings.Add(row.Key.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File.WriteAllText("offerings.json", JsonConvert.SerializeObject(offerings, Formatting.Indented));
|
}
|
||||||
|
}
|
||||||
|
var offeringsSerialized = new
|
||||||
|
{
|
||||||
|
Slashers = slasherOfferings.OrderBy(x => x).ToList(),
|
||||||
|
Campers = camperOfferings.OrderBy(x => x).ToList()
|
||||||
|
};
|
||||||
|
File.WriteAllText("offerings.json", JsonConvert.SerializeObject(offeringsSerialized, Formatting.Indented));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* perks
|
* perks
|
||||||
*/
|
*/
|
||||||
searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/PerkDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
searchPaths = dataPak.Files.Keys.Where(x => x.Contains($"/PerkDB.uasset", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
|
||||||
var perks = new List<string>();
|
var slasherPerks = new List<string>();
|
||||||
|
var camperPerks = new List<string>();
|
||||||
|
|
||||||
foreach (var path in searchPaths)
|
foreach (var path in searchPaths)
|
||||||
{
|
{
|
||||||
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
var cleanPath = path.Contains('.') ? path.Substring(0, path.LastIndexOf('.')) : path;
|
||||||
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
if (provider.TryLoadPackageObject<UDataTable>(cleanPath, out var dataTable))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"getting perks from {Path.GetFileName(path)}");
|
Console.WriteLine($"getting perks from {Path.GetFullPath(path)}");
|
||||||
|
|
||||||
foreach (var row in dataTable.RowMap)
|
foreach (var row in dataTable.RowMap)
|
||||||
perks.Add(row.Key.Text);
|
{
|
||||||
|
var roleProperty = row.Value.Properties.FirstOrDefault(p => p.Name.Text == "Role");
|
||||||
|
if (roleProperty?.Tag is EnumProperty roleEnumProp)
|
||||||
|
{
|
||||||
|
string roleName = roleEnumProp.Value.ToString() ?? "null";
|
||||||
|
if (roleName == "EPlayerRole::VE_Slasher")
|
||||||
|
slasherPerks.Add(row.Key.Text);
|
||||||
|
else
|
||||||
|
camperPerks.Add(row.Key.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File.WriteAllText("perks.json", JsonConvert.SerializeObject(perks, Formatting.Indented));
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var perksSerialized = new
|
||||||
|
{
|
||||||
|
Slashers = slasherPerks.OrderBy(x => x).ToList(),
|
||||||
|
Campers = camperPerks.OrderBy(x => x).ToList()
|
||||||
|
};
|
||||||
|
File.WriteAllText("perks.json", JsonConvert.SerializeObject(perksSerialized, Formatting.Indented));
|
||||||
|
|
||||||
Console.WriteLine("\nAll dumper operations finished. Press any key to Close.");
|
Console.WriteLine("\nAll dumper operations finished. Press any key to Close.");
|
||||||
Console.ReadKey();
|
Console.ReadKey();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
#include <processthreadsapi.h>
|
#include <processthreadsapi.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
std::string randomizeString(size_t length)
|
std::string randomizeString(size_t length)
|
||||||
{
|
{
|
||||||
@@ -133,8 +134,11 @@ bool CertManager::GenerateCA()
|
|||||||
|
|
||||||
Log::info("Generated new CA key and certificate files. Installing to Windows Root CA store automatically...");
|
Log::info("Generated new CA key and certificate files. Installing to Windows Root CA store automatically...");
|
||||||
|
|
||||||
STARTUPINFOA si = {sizeof(si)};
|
STARTUPINFOA si;
|
||||||
|
memset(&si, 0, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
PROCESS_INFORMATION pi;
|
PROCESS_INFORMATION pi;
|
||||||
|
memset(&pi, 0, sizeof(pi));
|
||||||
char cmd[] = "certutil.exe -user -addstore root ca_cert.pem";
|
char cmd[] = "certutil.exe -user -addstore root ca_cert.pem";
|
||||||
if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
|
if (CreateProcessA(NULL, cmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi))
|
||||||
{
|
{
|
||||||
@@ -191,8 +195,8 @@ SSL_CTX* CertManager::CreateHostContext(const std::string& host)
|
|||||||
|
|
||||||
SSL_CTX_set_alpn_select_cb(
|
SSL_CTX_set_alpn_select_cb(
|
||||||
ctx,
|
ctx,
|
||||||
[](SSL* ssl, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen,
|
[](SSL* /*ssl*/, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen,
|
||||||
void* arg) -> int {
|
void* /*arg*/) -> int {
|
||||||
for (unsigned int i = 0; i < inlen;)
|
for (unsigned int i = 0; i < inlen;)
|
||||||
{
|
{
|
||||||
unsigned int len = in[i];
|
unsigned int len = in[i];
|
||||||
|
|||||||
+10
-360
@@ -1,30 +1,10 @@
|
|||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
#include "spoofing.h"
|
||||||
|
|
||||||
#include <nerutils/log.h>
|
#include <nerutils/log.h>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <wininet.h>
|
#include <wininet.h>
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
#include <format>
|
|
||||||
#include <mutex>
|
|
||||||
#include <ctime>
|
|
||||||
#include <regex>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <simdjson.h>
|
|
||||||
|
|
||||||
std::string getExeDir()
|
|
||||||
{
|
|
||||||
char buffer[MAX_PATH];
|
|
||||||
GetModuleFileNameA(NULL, buffer, MAX_PATH);
|
|
||||||
std::string path(buffer);
|
|
||||||
size_t pos = path.find_last_of("\\/");
|
|
||||||
if (pos != std::string::npos) return path.substr(0, pos + 1);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool setProxy(bool enable, const std::string& proxyAddr)
|
bool setProxy(bool enable, const std::string& proxyAddr)
|
||||||
{
|
{
|
||||||
@@ -62,8 +42,8 @@ bool setProxy(bool enable, const std::string& proxyAddr)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Proxy* g_proxy = nullptr;
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
Proxy* g_Proxy = nullptr;
|
||||||
|
|
||||||
void cleanup()
|
void cleanup()
|
||||||
{
|
{
|
||||||
@@ -73,10 +53,10 @@ void cleanup()
|
|||||||
if (cleaned) return;
|
if (cleaned) return;
|
||||||
cleaned = true;
|
cleaned = true;
|
||||||
|
|
||||||
if (g_proxy)
|
if (g_Proxy)
|
||||||
{
|
{
|
||||||
Log::info("Shutting down proxy");
|
Log::info("Shutting down proxy");
|
||||||
g_proxy->Shutdown();
|
g_Proxy->Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info("Restoring system proxy settings");
|
Log::info("Restoring system proxy settings");
|
||||||
@@ -91,178 +71,10 @@ BOOL WINAPI consoleHandler(DWORD dwType)
|
|||||||
running = false;
|
running = false;
|
||||||
cleanup();
|
cleanup();
|
||||||
exit(0);
|
exit(0);
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mutex g_dataMutex;
|
|
||||||
std::vector<std::string> g_allObjectIds;
|
|
||||||
std::vector<std::string> g_allCharacterIds;
|
|
||||||
std::vector<std::string> g_stackableItems;
|
|
||||||
std::vector<std::string> g_uniqueItems;
|
|
||||||
std::vector<std::string> g_perks;
|
|
||||||
|
|
||||||
void loadDictDump(const std::string& path, std::vector<std::string>& outStackable, std::vector<std::string>& outUnique)
|
|
||||||
{
|
|
||||||
simdjson::dom::parser parser;
|
|
||||||
simdjson::dom::element doc;
|
|
||||||
auto error = parser.load(path).get(doc);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
Log::warning("Failed to open or parse dict dump {}: {}", path, simdjson::error_message(error));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
simdjson::dom::array itemsArr;
|
|
||||||
if (doc.at_key("Items").get(itemsArr) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
for (auto item : itemsArr)
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get(id) == simdjson::SUCCESS) outStackable.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
simdjson::dom::array powersArr;
|
|
||||||
if (doc.at_key("Powers").get(powersArr) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
for (auto item : powersArr)
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get(id) == simdjson::SUCCESS) outUnique.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadArrayDump(const std::string& path, std::vector<std::string>& outList)
|
|
||||||
{
|
|
||||||
simdjson::dom::parser parser;
|
|
||||||
simdjson::dom::element doc;
|
|
||||||
auto error = parser.load(path).get(doc);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
Log::warning("Failed to open or parse array dump {}: {}", path, simdjson::error_message(error));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
simdjson::dom::array arr;
|
|
||||||
if (doc.get(arr) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
for (auto item : arr)
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get(id) == simdjson::SUCCESS) outList.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadAllCustomItems()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
|
||||||
g_stackableItems.clear();
|
|
||||||
g_uniqueItems.clear();
|
|
||||||
g_perks.clear();
|
|
||||||
|
|
||||||
loadDictDump(getExeDir() + "addons.json", g_stackableItems, g_stackableItems);
|
|
||||||
loadDictDump(getExeDir() + "items.json", g_stackableItems, g_uniqueItems);
|
|
||||||
loadArrayDump(getExeDir() + "offerings.json", g_stackableItems);
|
|
||||||
loadArrayDump(getExeDir() + "perks.json", g_perks);
|
|
||||||
|
|
||||||
Log::info("Loaded {} stackable, {} unique (powers), and {} perk items from dumps", g_stackableItems.size(),
|
|
||||||
g_uniqueItems.size(), g_perks.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void parseCatalog(const std::string& data)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
simdjson::ondemand::parser parser;
|
|
||||||
simdjson::padded_string json(data);
|
|
||||||
auto doc = parser.iterate(json);
|
|
||||||
|
|
||||||
std::vector<std::string> newObjectIds;
|
|
||||||
std::vector<std::string> newCharacterIds;
|
|
||||||
|
|
||||||
auto items_data = doc["data"];
|
|
||||||
|
|
||||||
for (auto item : items_data["character"]["items"])
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get_string().get(id) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
newCharacterIds.push_back(std::string(id));
|
|
||||||
newObjectIds.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto item : items_data["shrine"]["items"])
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get_string().get(id) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
newObjectIds.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto item : items_data["item"]["items"])
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get_string().get(id) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
if (id.find("cell") == std::string_view::npos && id.find("Pack") == std::string_view::npos)
|
|
||||||
{
|
|
||||||
newObjectIds.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto item : items_data["outfit"]["items"])
|
|
||||||
{
|
|
||||||
std::string_view id;
|
|
||||||
if (item.get_string().get(id) == simdjson::SUCCESS)
|
|
||||||
{
|
|
||||||
newObjectIds.push_back(std::string(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
|
||||||
g_allObjectIds = std::move(newObjectIds);
|
|
||||||
g_allCharacterIds = std::move(newCharacterIds);
|
|
||||||
|
|
||||||
Log::info("Catalog parsed: {} items, {} characters", g_allObjectIds.size(), g_allCharacterIds.size());
|
|
||||||
}
|
|
||||||
catch (const std::exception& e)
|
|
||||||
{
|
|
||||||
Log::error("Failed to parse catalog: {}", e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateCatalog(const std::string& data)
|
|
||||||
{
|
|
||||||
std::string path = getExeDir() + "catalog_dump.json";
|
|
||||||
std::ofstream file(path);
|
|
||||||
file << data;
|
|
||||||
file.close();
|
|
||||||
Log::info("Raw catalog saved to {}", path);
|
|
||||||
|
|
||||||
parseCatalog(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadCatalogOnStartup()
|
|
||||||
{
|
|
||||||
std::string path = getExeDir() + "catalog_dump.json";
|
|
||||||
std::ifstream file(path);
|
|
||||||
if (file.is_open())
|
|
||||||
{
|
|
||||||
Log::info("Found catalog_dump.json - re-parsing raw dump with current filters...");
|
|
||||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
|
||||||
parseCatalog(content);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::warning("No catalog_dump.json found. Start the game and visit the store once to initialise the unlocker.");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
Log::createConsole();
|
Log::createConsole();
|
||||||
@@ -271,15 +83,12 @@ int main()
|
|||||||
|
|
||||||
Log::info("Init");
|
Log::info("Init");
|
||||||
|
|
||||||
loadCatalogOnStartup();
|
|
||||||
loadAllCustomItems();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
proxy setup
|
proxy setup
|
||||||
*/
|
*/
|
||||||
Log::info("Starting proxy");
|
Log::info("Starting proxy");
|
||||||
g_proxy = new Proxy();
|
g_Proxy = new Proxy();
|
||||||
if (!g_proxy->Init())
|
if (!g_Proxy->Init())
|
||||||
{
|
{
|
||||||
Log::error("Proxy failed to start");
|
Log::error("Proxy failed to start");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -287,171 +96,12 @@ int main()
|
|||||||
setProxy(true, std::format("127.0.0.1:{}", PROXY_PORT));
|
setProxy(true, std::format("127.0.0.1:{}", PROXY_PORT));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
listeners
|
Spoofer setup
|
||||||
*/
|
*/
|
||||||
g_proxy->OnServerResponse.addListener([](const std::string& url, std::string& data) {
|
Log::info("Spoofer init");
|
||||||
#ifdef _DEBUG
|
Spoofer* spoofer = new Spoofer();
|
||||||
if (url.find("bhvrdbd.com") != std::string::npos) Log::verbose("BHVR api res: {}", url);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos)
|
spoofer->init(g_Proxy);
|
||||||
updateCatalog(data);
|
|
||||||
else if (url.find("api/v1/dbd-inventories/all") != std::string::npos)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
|
||||||
if (!g_allObjectIds.empty())
|
|
||||||
{
|
|
||||||
Log::info("Merging catalog and dumped items into real inventory response");
|
|
||||||
|
|
||||||
size_t closePos = data.rfind("]}");
|
|
||||||
if (closePos != std::string::npos)
|
|
||||||
{
|
|
||||||
uint64_t now = time(nullptr);
|
|
||||||
std::string injected;
|
|
||||||
injected.reserve((g_allObjectIds.size() + g_stackableItems.size()) * 80);
|
|
||||||
|
|
||||||
std::unordered_set<std::string> seenIds;
|
|
||||||
size_t pos = 0;
|
|
||||||
while ((pos = data.find("\"objectId\":\"", pos)) != std::string::npos)
|
|
||||||
{
|
|
||||||
pos += 12;
|
|
||||||
size_t end = data.find("\"", pos);
|
|
||||||
if (end != std::string::npos)
|
|
||||||
{
|
|
||||||
seenIds.insert(data.substr(pos, end - pos));
|
|
||||||
pos = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto injectItem = [&](const std::string& id, int qty) {
|
|
||||||
if (id.empty() || seenIds.count(id)) return;
|
|
||||||
injected += std::format(",{{\"lastUpdateAt\":{},\"quantity\":{},\"objectId\":\"{}\"}}", now, qty, id);
|
|
||||||
seenIds.insert(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& id : g_allObjectIds) injectItem(id, 1);
|
|
||||||
for (const auto& id : g_stackableItems) injectItem(id, 100);
|
|
||||||
for (const auto& id : g_uniqueItems) injectItem(id, 1);
|
|
||||||
for (const auto& id : g_perks) injectItem(id, 3);
|
|
||||||
|
|
||||||
if (!injected.empty()) data.insert(closePos, injected);
|
|
||||||
|
|
||||||
Log::info("Injected {} items into global inventory",
|
|
||||||
std::count(injected.begin(), injected.end(), '{'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::warning("No catalog data available to inject into global inventory yet!");
|
|
||||||
}
|
|
||||||
else if (url.find("api/v1/dbd-character-data/") != std::string::npos)
|
|
||||||
{
|
|
||||||
std::vector<std::string> localStackable;
|
|
||||||
std::vector<std::string> localUnique;
|
|
||||||
std::vector<std::string> localPerks;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_dataMutex);
|
|
||||||
localStackable = g_stackableItems;
|
|
||||||
localUnique = g_uniqueItems;
|
|
||||||
localPerks = g_perks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!localStackable.empty() || !localUnique.empty() || !localPerks.empty())
|
|
||||||
{
|
|
||||||
std::unordered_set<std::string> perkSet(localPerks.begin(), localPerks.end());
|
|
||||||
std::unordered_set<std::string> uniqueSet(localUnique.begin(), localUnique.end());
|
|
||||||
std::regex qtyRegex(R"("quantity"\s*:\s*\d+)");
|
|
||||||
|
|
||||||
const char* targetArrays[] = {"\"characterItems\":[", "\"inventory\":[", "\"bloodwebRewards\":[",
|
|
||||||
"\"prestigeRewards\":["};
|
|
||||||
bool isBloodweb = url.find("/bloodweb") != std::string::npos;
|
|
||||||
size_t arraysModified = 0;
|
|
||||||
|
|
||||||
for (const char* arrayKey : targetArrays)
|
|
||||||
{
|
|
||||||
size_t pos = 0;
|
|
||||||
while ((pos = data.find(arrayKey, pos)) != std::string::npos)
|
|
||||||
{
|
|
||||||
pos += strlen(arrayKey);
|
|
||||||
size_t endPos = data.find("]", pos);
|
|
||||||
if (endPos == std::string::npos) break;
|
|
||||||
|
|
||||||
std::string currentItems = data.substr(pos, endPos - pos);
|
|
||||||
std::unordered_set<std::string> seenIds;
|
|
||||||
std::unordered_set<std::string> stackableSet(localStackable.begin(), localStackable.end());
|
|
||||||
|
|
||||||
size_t itemPos = 0;
|
|
||||||
while ((itemPos = currentItems.find("\"itemId\":\"", itemPos)) != std::string::npos)
|
|
||||||
{
|
|
||||||
size_t idStart = itemPos + 10;
|
|
||||||
size_t idEnd = currentItems.find("\"", idStart);
|
|
||||||
if (idEnd == std::string::npos) break;
|
|
||||||
|
|
||||||
std::string id = currentItems.substr(idStart, idEnd - idStart);
|
|
||||||
seenIds.insert(id);
|
|
||||||
|
|
||||||
int qty = -1;
|
|
||||||
if (perkSet.count(id))
|
|
||||||
qty = 3;
|
|
||||||
else if (uniqueSet.count(id))
|
|
||||||
qty = 1;
|
|
||||||
else if (stackableSet.count(id))
|
|
||||||
qty = 100;
|
|
||||||
|
|
||||||
if (qty != -1)
|
|
||||||
{
|
|
||||||
size_t objStart = currentItems.rfind("{", itemPos);
|
|
||||||
size_t objEnd = currentItems.find("}", itemPos);
|
|
||||||
if (objStart != std::string::npos && objEnd != std::string::npos && objStart < objEnd)
|
|
||||||
{
|
|
||||||
std::string objStr = currentItems.substr(objStart, objEnd - objStart + 1);
|
|
||||||
if (std::regex_search(objStr, qtyRegex))
|
|
||||||
objStr =
|
|
||||||
std::regex_replace(objStr, qtyRegex, "\"quantity\":" + std::to_string(qty));
|
|
||||||
else
|
|
||||||
objStr.insert(objStr.length() - 1, ",\"quantity\":" + std::to_string(qty));
|
|
||||||
|
|
||||||
currentItems.replace(objStart, objEnd - objStart + 1, objStr);
|
|
||||||
itemPos = objStart + objStr.length();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
itemPos = idEnd;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
itemPos = idEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto injectIfMissing = [&](const std::vector<std::string>& list, int qty) {
|
|
||||||
for (const auto& id : list)
|
|
||||||
{
|
|
||||||
if (seenIds.find(id) == seenIds.end())
|
|
||||||
{
|
|
||||||
if (!currentItems.empty() && currentItems.back() != ',') currentItems += ",";
|
|
||||||
currentItems +=
|
|
||||||
"{\"itemId\":\"" + id + "\",\"quantity\":" + std::to_string(qty) + "}";
|
|
||||||
seenIds.insert(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isBloodweb && (std::string_view(arrayKey) == "\"characterItems\":[" ||
|
|
||||||
std::string_view(arrayKey) == "\"inventory\":["))
|
|
||||||
{
|
|
||||||
injectIfMissing(localStackable, 100);
|
|
||||||
injectIfMissing(localUnique, 1);
|
|
||||||
injectIfMissing(localPerks, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.replace(pos, endPos - pos, currentItems);
|
|
||||||
pos += currentItems.length();
|
|
||||||
arraysModified++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log::info("Spoofed items in {} character data arrays across response", arraysModified);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log::warning("No custom dumped items available to inject into character data!");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
pause
|
pause
|
||||||
|
|||||||
+47
-31
@@ -331,56 +331,72 @@ void Proxy::handleClient(SOCKET hClientSocket)
|
|||||||
removeHeader(headers, "Expect");
|
removeHeader(headers, "Expect");
|
||||||
headers.insert(headers.size() - 2, "Accept-Encoding: identity\r\n");
|
headers.insert(headers.size() - 2, "Accept-Encoding: identity\r\n");
|
||||||
|
|
||||||
OnClientRequest.run(url, headers);
|
if (clientStream.contentLength == 0 || (clientStream.contentLength < 0 && !clientStream.isChunked))
|
||||||
SSL_write(remoteSSL, headers.data(), (int)headers.size());
|
{
|
||||||
|
std::string emptyBody = "";
|
||||||
|
OnClientRequest.run(url, emptyBody, headers);
|
||||||
|
|
||||||
|
if (!pendingUrls.empty()) pendingUrls.back() = url;
|
||||||
|
|
||||||
|
SSL_write(remoteSSL, headers.data(), (int)headers.size());
|
||||||
clientStream.buffer.erase(0, clientStream.headersEnd + 4);
|
clientStream.buffer.erase(0, clientStream.headersEnd + 4);
|
||||||
|
clientStream.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientStream.isReceivingBody)
|
if (clientStream.isReceivingBody)
|
||||||
{
|
{
|
||||||
|
size_t bodyStart = clientStream.headersEnd + 4;
|
||||||
|
std::string url = pendingUrls.back();
|
||||||
|
std::string headers = clientStream.buffer.substr(0, bodyStart);
|
||||||
|
removeHeader(headers, "Accept-Encoding");
|
||||||
|
removeHeader(headers, "Expect");
|
||||||
|
headers.insert(headers.size() - 2, "Accept-Encoding: identity\r\n");
|
||||||
|
|
||||||
|
bool complete = false;
|
||||||
|
std::string body;
|
||||||
|
|
||||||
if (clientStream.isChunked)
|
if (clientStream.isChunked)
|
||||||
{
|
{
|
||||||
size_t idx = 0;
|
size_t idx = bodyStart;
|
||||||
while (idx < clientStream.buffer.size())
|
while (idx < clientStream.buffer.size())
|
||||||
{
|
{
|
||||||
size_t le = clientStream.buffer.find("\r\n", idx);
|
size_t le = clientStream.buffer.find("\r\n", idx);
|
||||||
if (le == std::string::npos) break;
|
if (le == std::string::npos) break;
|
||||||
|
int cs = safe_stoi(clientStream.buffer.substr(idx, le - idx), 0, 16);
|
||||||
int chunkSz = safe_stoi(clientStream.buffer.substr(idx, le - idx), 0, 16);
|
if (idx + (le - idx) + 2 + cs + 2 > clientStream.buffer.size()) break;
|
||||||
size_t totalChunkSz = (le - idx) + 2 + chunkSz + 2;
|
body.append(clientStream.buffer, le + 2, cs);
|
||||||
if (idx + totalChunkSz > clientStream.buffer.size()) break;
|
idx = le + 2 + cs + 2;
|
||||||
|
if (cs == 0)
|
||||||
SSL_write(remoteSSL, clientStream.buffer.data() + idx, (int)totalChunkSz);
|
|
||||||
idx += totalChunkSz;
|
|
||||||
|
|
||||||
if (chunkSz == 0)
|
|
||||||
{
|
{
|
||||||
clientStream.reset();
|
complete = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (idx > 0) clientStream.buffer.erase(0, idx);
|
|
||||||
if (!clientStream.isReceivingBody) continue;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
else if (clientStream.contentLength >= 0)
|
else if (clientStream.contentLength >= 0)
|
||||||
{
|
{
|
||||||
size_t ts = (std::min)((size_t)clientStream.contentLength, clientStream.buffer.size());
|
if (clientStream.buffer.size() >= bodyStart + clientStream.contentLength)
|
||||||
if (ts > 0)
|
|
||||||
{
|
{
|
||||||
SSL_write(remoteSSL, clientStream.buffer.data(), (int)ts);
|
body = clientStream.buffer.substr(bodyStart, clientStream.contentLength);
|
||||||
clientStream.buffer.erase(0, ts);
|
complete = true;
|
||||||
clientStream.contentLength -= (int)ts;
|
|
||||||
}
|
}
|
||||||
if (clientStream.contentLength <= 0)
|
}
|
||||||
|
|
||||||
|
if (complete)
|
||||||
|
{
|
||||||
|
OnClientRequest.run(url, body, headers);
|
||||||
|
if (!pendingUrls.empty() && pendingUrls.back() != url) pendingUrls.back() = url;
|
||||||
|
|
||||||
|
SSL_write(remoteSSL, headers.data(), (int)headers.size());
|
||||||
|
SSL_write(remoteSSL, clientStream.buffer.data() + bodyStart,
|
||||||
|
(int)(clientStream.buffer.size() - bodyStart));
|
||||||
|
clientStream.buffer.clear();
|
||||||
clientStream.reset();
|
clientStream.reset();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
clientStream.reset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,15 +539,15 @@ void Proxy::handleClient(SOCKET hClientSocket)
|
|||||||
if (!pendingUrls.empty()) pendingUrls.pop_front();
|
if (!pendingUrls.empty()) pendingUrls.pop_front();
|
||||||
|
|
||||||
std::string respHeaders = serverStream.buffer.substr(0, bStart);
|
std::string respHeaders = serverStream.buffer.substr(0, bStart);
|
||||||
size_t firstSpace = respHeaders.find(' ');
|
OnServerResponse.run(url, body, respHeaders);
|
||||||
int sc =
|
|
||||||
(firstSpace != std::string::npos) ? safe_stoi(respHeaders.substr(firstSpace + 1, 3)) : 0;
|
|
||||||
|
|
||||||
OnServerResponse.run(url, body);
|
|
||||||
|
|
||||||
removeHeader(respHeaders, "Transfer-Encoding");
|
removeHeader(respHeaders, "Transfer-Encoding");
|
||||||
removeHeader(respHeaders, "Content-Length");
|
removeHeader(respHeaders, "Content-Length");
|
||||||
if (sc != 204 && sc != 304 && sc != 205)
|
|
||||||
|
size_t fs = respHeaders.find(' ');
|
||||||
|
int scFinal = (fs != std::string::npos) ? safe_stoi(respHeaders.substr(fs + 1, 3)) : 0;
|
||||||
|
|
||||||
|
if (scFinal != 204 && scFinal != 304 && scFinal != 205)
|
||||||
respHeaders.insert(respHeaders.size() - 2,
|
respHeaders.insert(respHeaders.size() - 2,
|
||||||
"Content-Length: " + std::to_string(body.size()) + "\r\n");
|
"Content-Length: " + std::to_string(body.size()) + "\r\n");
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class Proxy
|
|||||||
bool Init();
|
bool Init();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
CallbackEvent<const std::string&, std::string&> OnClientRequest;
|
CallbackEvent<std::string&, const std::string&, std::string&> OnClientRequest;
|
||||||
CallbackEvent<const std::string&, std::string&> OnServerResponse;
|
CallbackEvent<const std::string&, std::string&, std::string&> OnServerResponse;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loop();
|
void loop();
|
||||||
|
|||||||
@@ -0,0 +1,540 @@
|
|||||||
|
#include "spoofing.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <minwindef.h>
|
||||||
|
|
||||||
|
#include <nerutils/log.h>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
std::string getExeDir()
|
||||||
|
{
|
||||||
|
char buffer[MAX_PATH];
|
||||||
|
GetModuleFileNameA(NULL, buffer, MAX_PATH);
|
||||||
|
std::string path(buffer);
|
||||||
|
size_t pos = path.find_last_of("\\/");
|
||||||
|
if (pos != std::string::npos) return path.substr(0, pos + 1);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::init(Proxy* proxy)
|
||||||
|
{
|
||||||
|
loadData();
|
||||||
|
registerListeners(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::registerListeners(Proxy* proxy)
|
||||||
|
{
|
||||||
|
proxy->OnServerResponse.addListener([this](const std::string& url, std::string& body, std::string& respHeaders) {
|
||||||
|
this->serverResponseHandler(url, body, respHeaders);
|
||||||
|
});
|
||||||
|
|
||||||
|
proxy->OnClientRequest.addListener([this](std::string& url, const std::string& body, std::string& reqHeaders) {
|
||||||
|
this->clientRequestHandler(url, body, reqHeaders);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::loadData()
|
||||||
|
{
|
||||||
|
Log::info("Loading dump data");
|
||||||
|
|
||||||
|
std::string catalogDumpPath = getExeDir() + "catalog.json";
|
||||||
|
std::ifstream catalogFile(catalogDumpPath);
|
||||||
|
if (catalogFile.is_open())
|
||||||
|
{
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << catalogFile.rdbuf();
|
||||||
|
parseCatalog(buffer.str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::warning("Missing catalog.json");
|
||||||
|
|
||||||
|
std::string itemDumpPath = getExeDir() + "items.json";
|
||||||
|
std::ifstream itemFile(itemDumpPath);
|
||||||
|
if (itemFile.is_open())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string content((std::istreambuf_iterator<char>(itemFile)), std::istreambuf_iterator<char>());
|
||||||
|
json doc = json::parse(content);
|
||||||
|
|
||||||
|
if (doc.contains("Camper") && doc["Camper"].is_object())
|
||||||
|
if (doc["Camper"].contains("Items") && doc["Camper"]["Items"].is_array())
|
||||||
|
for (const auto& item : doc["Camper"]["Items"])
|
||||||
|
if (item.is_string()) _camperItemIds.insert(item.get<std::string>());
|
||||||
|
|
||||||
|
if (doc.contains("Slasher") && doc["Slasher"].is_object())
|
||||||
|
if (doc["Slasher"].contains("Powers") && doc["Slasher"]["Powers"].is_array())
|
||||||
|
for (const auto& item : doc["Slasher"]["Powers"])
|
||||||
|
if (item.is_string()) _slasherPowerIds.insert(item.get<std::string>());
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", "items.json", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::warning("Missing items.json");
|
||||||
|
|
||||||
|
std::string offeringDumpPath = getExeDir() + "offerings.json";
|
||||||
|
std::ifstream offeringsFile(offeringDumpPath);
|
||||||
|
if (offeringsFile.is_open())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string content((std::istreambuf_iterator<char>(offeringsFile)), std::istreambuf_iterator<char>());
|
||||||
|
json doc = json::parse(content);
|
||||||
|
|
||||||
|
if (doc.contains("Slashers") && doc["Slashers"].is_array())
|
||||||
|
for (const auto& offering : doc["Slashers"])
|
||||||
|
if (offering.is_string()) _slasherOfferingIds.insert(offering.get<std::string>());
|
||||||
|
|
||||||
|
if (doc.contains("Campers") && doc["Campers"].is_array())
|
||||||
|
for (const auto& offering : doc["Campers"])
|
||||||
|
if (offering.is_string()) _camperOfferingIds.insert(offering.get<std::string>());
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", "offerings.json", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::warning("Missing offerings.json");
|
||||||
|
|
||||||
|
std::string addonDumpPath = getExeDir() + "addons.json";
|
||||||
|
std::ifstream addonFile(addonDumpPath);
|
||||||
|
if (addonFile.is_open())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string content((std::istreambuf_iterator<char>(addonFile)), std::istreambuf_iterator<char>());
|
||||||
|
json doc = json::parse(content);
|
||||||
|
|
||||||
|
if (doc.contains("Slashers") && doc["Slashers"].is_array())
|
||||||
|
for (const auto& item : doc["Slashers"])
|
||||||
|
if (item.is_string()) _slasherAddonIds.insert(item.get<std::string>());
|
||||||
|
|
||||||
|
if (doc.contains("Campers") && doc["Campers"].is_array())
|
||||||
|
for (const auto& power : doc["Campers"])
|
||||||
|
if (power.is_string()) _camperAddonIds.insert(power.get<std::string>());
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", "addons.json", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::warning("Missing addons.json");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string perkDumpPaths = getExeDir() + "perks.json";
|
||||||
|
std::ifstream perkFile(perkDumpPaths);
|
||||||
|
if (perkFile.is_open())
|
||||||
|
{
|
||||||
|
std::string content((std::istreambuf_iterator<char>(perkFile)), std::istreambuf_iterator<char>());
|
||||||
|
json doc = json::parse(content);
|
||||||
|
|
||||||
|
if (doc.contains("Slashers") && doc["Slashers"].is_array())
|
||||||
|
for (const auto& item : doc["Slashers"])
|
||||||
|
if (item.is_string()) _slasherPerkIds.insert(item.get<std::string>());
|
||||||
|
|
||||||
|
if (doc.contains("Campers") && doc["Campers"].is_array())
|
||||||
|
for (const auto& power : doc["Campers"])
|
||||||
|
if (power.is_string()) _camperPerkIds.insert(power.get<std::string>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log::warning("Missing perks.json");
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", "perks.json", e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info("Loaded: Camper items={}, Slasher powers={}, "
|
||||||
|
"Slasher addons={}, Camper addons={}, Camper perks={}, Slasher perks={}"
|
||||||
|
"Slasher offerings={}, Camper offerings={}",
|
||||||
|
_camperItemIds.size(), _slasherPowerIds.size(), _slasherAddonIds.size(), _camperAddonIds.size(),
|
||||||
|
_camperPerkIds.size(), _slasherPerkIds.size(), _slasherOfferingIds.size(), _camperOfferingIds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::parseCatalog(std::string data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json doc = json::parse(data);
|
||||||
|
|
||||||
|
if (doc.contains("data")) doc = doc["data"];
|
||||||
|
|
||||||
|
if (doc.contains("item") && doc["item"].contains("items") && doc["item"]["items"].is_array())
|
||||||
|
for (const auto& item : doc["item"]["items"])
|
||||||
|
if (item.is_string()) _catalogItemIds.insert(item.get<std::string>());
|
||||||
|
|
||||||
|
if (doc.contains("outfit") && doc["outfit"].contains("items") && doc["outfit"]["items"].is_array())
|
||||||
|
for (const auto& item : doc["outfit"]["items"])
|
||||||
|
if (item.is_string()) _catalogOutfitIds.insert(item.get<std::string>());
|
||||||
|
|
||||||
|
Log::info("Parsed {} items and {} outfits from catalog", _catalogItemIds.size(), _catalogOutfitIds.size());
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", "parseCatalog", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::parseAndDumpCatalog(std::string& data)
|
||||||
|
{
|
||||||
|
std::string path = getExeDir() + "catalog.json";
|
||||||
|
std::ofstream file(path);
|
||||||
|
file << data;
|
||||||
|
file.close();
|
||||||
|
Log::info("Raw catalog saved to {}", path);
|
||||||
|
|
||||||
|
parseCatalog(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> killerNames = {
|
||||||
|
"Chuckles", "Bob", "HillBilly", "Nurse", "Shape", "Witch", "Killer07", "Cannibal", "Bear",
|
||||||
|
"Nightmare", "Pig", "Clown", "Spirit", "Plague", "Ghostface", "Demogorgon", "Oni", "Gunslinger"};
|
||||||
|
|
||||||
|
void Spoofer::modifyCharacterData(json& js)
|
||||||
|
{
|
||||||
|
std::unordered_set<std::string> existingItemIds;
|
||||||
|
bool isSlasher = false;
|
||||||
|
|
||||||
|
if (js.contains("characterName") && js["characterName"].is_string())
|
||||||
|
{
|
||||||
|
std::string name = js["characterName"];
|
||||||
|
if (killerNames.contains(name) || (name.length() == 3 && name[0] == 'K')) isSlasher = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (js.contains("prestigeLevel") && js["prestigeLevel"].get<int>() <= 1)
|
||||||
|
if (js.contains("bloodWebLevel") && js["bloodWebLevel"].get<int>() <= 15) js["bloodWebLevel"] = 16;
|
||||||
|
|
||||||
|
if (js.contains("bloodWebData"))
|
||||||
|
{
|
||||||
|
static std::random_device rd;
|
||||||
|
static std::mt19937 rng(rd());
|
||||||
|
|
||||||
|
std::unordered_set<std::string> stackableIds;
|
||||||
|
stackableIds.insert(_camperItemIds.begin(), _camperItemIds.end());
|
||||||
|
stackableIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end());
|
||||||
|
stackableIds.insert(_camperAddonIds.begin(), _camperAddonIds.end());
|
||||||
|
stackableIds.insert(_slasherAddonIds.begin(), _slasherAddonIds.end());
|
||||||
|
stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end());
|
||||||
|
|
||||||
|
std::string selectedContentId = "Spring2024Offering";
|
||||||
|
auto it = stackableIds.begin();
|
||||||
|
std::advance(it, std::uniform_int_distribution<>(0, static_cast<int>(_camperOfferingIds.size()) - 1)(rng));
|
||||||
|
selectedContentId = *it;
|
||||||
|
|
||||||
|
std::vector<std::string> paths;
|
||||||
|
json ringDataArray = json::array();
|
||||||
|
|
||||||
|
ringDataArray.push_back({{"nodeData", json::array({{{"nodeId", "0"}, {"state", "Collected"}}})}});
|
||||||
|
|
||||||
|
int nodesPerRing[] = {6, 12, 12};
|
||||||
|
std::vector<std::string> prevRingNodes = {"0"};
|
||||||
|
|
||||||
|
for (int ring = 1; ring <= 3; ++ring)
|
||||||
|
{
|
||||||
|
json nodeDataArray = json::array();
|
||||||
|
std::vector<std::string> currentRingNodes;
|
||||||
|
int numNodes = nodesPerRing[ring - 1];
|
||||||
|
|
||||||
|
for (int i = 1; i <= numNodes; ++i)
|
||||||
|
{
|
||||||
|
std::string childId = std::to_string((ring * 100) + i);
|
||||||
|
currentRingNodes.push_back(childId);
|
||||||
|
|
||||||
|
int parentIndex = (i - 1) / (numNodes / static_cast<int>(prevRingNodes.size()));
|
||||||
|
|
||||||
|
if (parentIndex >= static_cast<int>(prevRingNodes.size()))
|
||||||
|
parentIndex = static_cast<int>(prevRingNodes.size()) - 1;
|
||||||
|
|
||||||
|
std::string parentId = prevRingNodes[parentIndex];
|
||||||
|
paths.push_back(parentId + "_" + childId);
|
||||||
|
|
||||||
|
nodeDataArray.push_back(
|
||||||
|
{{"nodeId", childId}, {"state", "Collected"}, {"contentId", selectedContentId}});
|
||||||
|
}
|
||||||
|
|
||||||
|
ringDataArray.push_back({{"nodeData", nodeDataArray}});
|
||||||
|
prevRingNodes = std::move(currentRingNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
js["bloodWebData"]["paths"] = paths;
|
||||||
|
js["bloodWebData"]["ringData"] = ringDataArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (js.contains("characterItems") && js["characterItems"].is_array())
|
||||||
|
{
|
||||||
|
std::unordered_set<std::string> stackableIds;
|
||||||
|
stackableIds.insert(_camperItemIds.begin(), _camperItemIds.end());
|
||||||
|
stackableIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end());
|
||||||
|
stackableIds.insert(_camperAddonIds.begin(), _camperAddonIds.end());
|
||||||
|
stackableIds.insert(_slasherAddonIds.begin(), _slasherAddonIds.end());
|
||||||
|
stackableIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end());
|
||||||
|
|
||||||
|
for (auto& item : js["characterItems"])
|
||||||
|
{
|
||||||
|
if (item.contains("itemId") && item["itemId"].is_string())
|
||||||
|
{
|
||||||
|
std::string itemId = item["itemId"];
|
||||||
|
existingItemIds.insert(itemId);
|
||||||
|
|
||||||
|
if (_slasherPowerIds.find(itemId) != _slasherPowerIds.end())
|
||||||
|
isSlasher = true;
|
||||||
|
else if (stackableIds.contains(itemId))
|
||||||
|
item["quantity"] = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSlasher)
|
||||||
|
{
|
||||||
|
for (const std::string& itemId : _camperItemIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}});
|
||||||
|
for (const std::string& itemId : _camperAddonIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}});
|
||||||
|
for (const std::string& itemId : _camperOfferingIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}});
|
||||||
|
for (const std::string& itemId : _camperPerkIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 3}});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const std::string& itemId : _slasherAddonIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}});
|
||||||
|
for (const std::string& itemId : _slasherOfferingIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 100}});
|
||||||
|
for (const std::string& itemId : _slasherPerkIds)
|
||||||
|
if (existingItemIds.find(itemId) == existingItemIds.end())
|
||||||
|
js["characterItems"].push_back({{"itemId", itemId}, {"quantity", 3}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::serverResponseHandler(const std::string& url, std::string& body, std::string& respHeaders)
|
||||||
|
{
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (url.find("bhvrdbd.com") != std::string::npos) Log::verbose("BHVR api res @ {}", url);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (url.find("api/v1/extensions/store/getCatalogItems") != std::string::npos) return parseAndDumpCatalog(body);
|
||||||
|
|
||||||
|
if (url.find("api/v1/dbd-inventories/all") != std::string::npos)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json doc = json::parse(body);
|
||||||
|
auto& itemsArr = doc["inventoryItems"];
|
||||||
|
|
||||||
|
std::unordered_set<std::string> foundObjects;
|
||||||
|
std::unordered_set<std::string> foundAddons;
|
||||||
|
std::unordered_set<std::string> foundPerks;
|
||||||
|
std::unordered_set<std::string> foundOfferings;
|
||||||
|
std::unordered_set<std::string> foundCatalogItems;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> offeringIds;
|
||||||
|
offeringIds.insert(_camperOfferingIds.begin(), _camperOfferingIds.end());
|
||||||
|
offeringIds.insert(_slasherOfferingIds.begin(), _slasherOfferingIds.end());
|
||||||
|
|
||||||
|
std::unordered_set<std::string> catalogIds;
|
||||||
|
catalogIds.insert(_catalogOutfitIds.begin(), _catalogOutfitIds.end());
|
||||||
|
catalogIds.insert(_catalogItemIds.begin(), _catalogItemIds.end());
|
||||||
|
|
||||||
|
std::unordered_set<std::string> perkIds;
|
||||||
|
perkIds.insert(_slasherPerkIds.begin(), _slasherPerkIds.end());
|
||||||
|
perkIds.insert(_camperPerkIds.begin(), _camperPerkIds.end());
|
||||||
|
|
||||||
|
for (auto& item : itemsArr)
|
||||||
|
{
|
||||||
|
std::string objectId = item["objectId"];
|
||||||
|
|
||||||
|
if (perkIds.find(objectId) != perkIds.end())
|
||||||
|
{
|
||||||
|
foundPerks.insert(objectId);
|
||||||
|
item["quantity"] = 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offeringIds.find(objectId) != offeringIds.end())
|
||||||
|
{
|
||||||
|
foundOfferings.insert(objectId);
|
||||||
|
item["quantity"] = 100;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (catalogIds.find(objectId) != catalogIds.end())
|
||||||
|
{
|
||||||
|
foundCatalogItems.insert(objectId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& id : perkIds)
|
||||||
|
{
|
||||||
|
if (foundPerks.find(id) == foundPerks.end())
|
||||||
|
{
|
||||||
|
itemsArr.push_back({
|
||||||
|
{"objectId", id},
|
||||||
|
{"quantity", 3},
|
||||||
|
{"lastUpdateAt", std::time(nullptr)},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& id : offeringIds)
|
||||||
|
{
|
||||||
|
if (foundOfferings.find(id) == foundOfferings.end())
|
||||||
|
{
|
||||||
|
itemsArr.push_back({
|
||||||
|
{"objectId", id},
|
||||||
|
{"quantity", 100},
|
||||||
|
{"lastUpdateAt", std::time(nullptr)},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& id : catalogIds)
|
||||||
|
{
|
||||||
|
if (foundCatalogItems.find(id) == foundCatalogItems.end())
|
||||||
|
{
|
||||||
|
itemsArr.push_back({
|
||||||
|
{"objectId", id},
|
||||||
|
{"quantity", 1},
|
||||||
|
{"lastUpdateAt", std::time(nullptr)},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string updatedJson = doc.dump();
|
||||||
|
body = updatedJson;
|
||||||
|
|
||||||
|
Log::verbose("Inventory updated: Items={}, Addons={}, Perks={}, Offerings={}", foundObjects.size(),
|
||||||
|
foundAddons.size(), foundPerks.size(), foundOfferings.size());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", url, e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.find("api/v1/dbd-character-data/get-all") != std::string::npos)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json doc = json::parse(body);
|
||||||
|
auto& charList = doc["list"];
|
||||||
|
|
||||||
|
for (auto& charInfo : charList)
|
||||||
|
{
|
||||||
|
if (charInfo.contains("bloodWebLevel") && charInfo.at("bloodWebLevel").get<int>() <= 15)
|
||||||
|
if (!charInfo.contains("prestigeLevel") || charInfo.at("prestigeLevel").get<int>() <= 0)
|
||||||
|
charInfo["bloodWebLevel"] = 16;
|
||||||
|
|
||||||
|
if (charInfo.contains("bloodWebData") && charInfo.at("bloodWebData").contains("level"))
|
||||||
|
charInfo["bloodWebData"]["level"] = 1;
|
||||||
|
|
||||||
|
if (charInfo["isEntitled"] == false)
|
||||||
|
{
|
||||||
|
charInfo["isEntitled"] = true;
|
||||||
|
if (charInfo.contains("origin"))
|
||||||
|
{
|
||||||
|
charInfo["purchaseInfo"] = {{"quantity", 1},
|
||||||
|
{"origin", "PlayerInventory"},
|
||||||
|
{"reason", "Item(s) added via Purchase"},
|
||||||
|
{"lastUpdateAt", 1770702482},
|
||||||
|
{"objectId", charInfo["characterName"]}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyCharacterData(charInfo);
|
||||||
|
}
|
||||||
|
body = doc.dump();
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", url, e.what());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.find("api/v1/dbd-character-data/") != std::string::npos)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json doc = json::parse(body);
|
||||||
|
|
||||||
|
if (body.find("NotAllowedException") != std::string::npos && body.find("not owned") != std::string::npos)
|
||||||
|
{
|
||||||
|
Log::info("Spoofing bloodweb error for unowned character");
|
||||||
|
json mock;
|
||||||
|
mock["bloodWebLevelChanged"] = false;
|
||||||
|
mock["updatedWallets"] = json::array();
|
||||||
|
mock["bloodWebLevel"] = 16;
|
||||||
|
mock["prestigeLevel"] = 0;
|
||||||
|
mock["bloodWebData"] = {{"ringData", json::array()}, {"paths", json::array()}};
|
||||||
|
mock["characterItems"] = json::array();
|
||||||
|
mock["characterName"] = this->_lastBloodWebChar;
|
||||||
|
|
||||||
|
modifyCharacterData(mock);
|
||||||
|
|
||||||
|
size_t firstSpace = respHeaders.find(' ');
|
||||||
|
if (firstSpace != std::string::npos) respHeaders.replace(firstSpace + 1, 3, "200");
|
||||||
|
|
||||||
|
body = mock.dump();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
modifyCharacterData(doc);
|
||||||
|
body = doc.dump();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
Log::error("JSON parse error in {}: {}", url, e.what());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spoofer::clientRequestHandler(std::string& url, const std::string& body, std::string& /*reqHeaders*/)
|
||||||
|
{
|
||||||
|
if (url.find("api/v1/dbd-character-data/bloodweb") != std::string::npos ||
|
||||||
|
url.find("api/v1/dbd-character-data/bulk-spending-bloodweb") != std::string::npos)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json req = json::parse(body);
|
||||||
|
if (req.contains("characterName"))
|
||||||
|
{
|
||||||
|
this->_lastBloodWebChar = req["characterName"];
|
||||||
|
Log::info("Detected bloodweb request for character: {}", this->_lastBloodWebChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "proxy.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
||||||
|
class Spoofer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init(Proxy* proxy);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void registerListeners(Proxy* proxy);
|
||||||
|
void loadData();
|
||||||
|
|
||||||
|
void parseCatalog(std::string data);
|
||||||
|
|
||||||
|
void parseAndDumpCatalog(std::string& data);
|
||||||
|
void modifyCharacterData(nlohmann::json& js);
|
||||||
|
|
||||||
|
void serverResponseHandler(const std::string& url, std::string& body, std::string& respHeaders);
|
||||||
|
void clientRequestHandler(std::string& url, const std::string& body, std::string& reqHeaders);
|
||||||
|
|
||||||
|
std::unordered_set<std::string> _camperItemIds;
|
||||||
|
std::unordered_set<std::string> _slasherPowerIds;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> _camperOfferingIds;
|
||||||
|
std::unordered_set<std::string> _slasherOfferingIds;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> _camperAddonIds;
|
||||||
|
std::unordered_set<std::string> _slasherAddonIds;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> _slasherPerkIds;
|
||||||
|
std::unordered_set<std::string> _camperPerkIds;
|
||||||
|
|
||||||
|
std::unordered_set<std::string> _catalogOutfitIds;
|
||||||
|
std::unordered_set<std::string> _catalogItemIds;
|
||||||
|
|
||||||
|
std::string _lastBloodWebChar = "Ace";
|
||||||
|
};
|
||||||
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
add_subdirectory(nerutils)
|
add_subdirectory(nerutils)
|
||||||
add_subdirectory(simdjson)
|
add_subdirectory(json)
|
||||||
|
|||||||
+1
Submodule vendor/json added at eba0a92bfb
Vendored
-1
Submodule vendor/simdjson deleted from 5395aacb8b
Reference in New Issue
Block a user