diff --git a/src/client/component/demonware.cpp b/src/client/component/demonware.cpp index 57dd5d64..4a83ef60 100644 --- a/src/client/component/demonware.cpp +++ b/src/client/component/demonware.cpp @@ -501,7 +501,7 @@ namespace demonware void post_unpack() override { #ifdef DW_DEBUG - //utils::hook::jump(0x141285040, bd_logger_stub, true); + utils::hook::jump(0x141285040, bd_logger_stub, true); #endif utils::hook::set(0x14B5BB96F, 0x0); // CURLOPT_SSL_VERIFYPEER diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index ce0baad8..ef92888d 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -267,7 +267,8 @@ namespace patches dvars::override::register_int("dvl", 0, 0, 0, game::DVAR_FLAG_READ); // killswitches - dvars::override::register_bool("killswitch_store", true, game::DVAR_FLAG_READ); + dvars::override::register_bool("mission_team_contracts_enabled", true, game::DVAR_FLAG_READ); + //dvars::override::register_bool("killswitch_store", true, game::DVAR_FLAG_READ); dvars::override::register_bool("killswitch_matchID", true, game::DVAR_FLAG_READ); // announcer packs diff --git a/src/client/game/demonware/byte_buffer.cpp b/src/client/game/demonware/byte_buffer.cpp index af8f8202..dafc1b2e 100644 --- a/src/client/game/demonware/byte_buffer.cpp +++ b/src/client/game/demonware/byte_buffer.cpp @@ -128,6 +128,24 @@ namespace demonware return true; } + bool byte_buffer::read_struct(void* output) + { + if (!this->read_data_type(BD_BB_STRUCTURED_DATA_TYPE)) + { + return false; + } + + unsigned int size; + this->read_uint32(&size); + + auto data = const_cast(this->buffer_.data()) + this->current_byte_; + memcpy(output, data, size); + + this->current_byte_ += size; + + return true; + } + bool byte_buffer::read_data_type(const unsigned char expected) { if (!this->use_data_types_) return true; @@ -248,6 +266,14 @@ namespace demonware return this->write(length, data); } + bool byte_buffer::write_struct(void* data, const int length) + { + this->write_data_type(BD_BB_STRUCTURED_DATA_TYPE); + this->write_uint32(length); + + return this->write(length, data); + } + bool byte_buffer::write_array_header(const unsigned char type, const unsigned int element_count, const unsigned int element_size) { diff --git a/src/client/game/demonware/byte_buffer.hpp b/src/client/game/demonware/byte_buffer.hpp index 85f7d1a2..7be88215 100644 --- a/src/client/game/demonware/byte_buffer.hpp +++ b/src/client/game/demonware/byte_buffer.hpp @@ -26,6 +26,7 @@ namespace demonware bool read_string(std::string* output); bool read_blob(char** output, int* length); bool read_blob(std::string* output); + bool read_struct(void* output); bool read_data_type(unsigned char expected); bool read_array_header(unsigned char expected, unsigned int* element_count, @@ -46,6 +47,7 @@ namespace demonware bool write_string(const std::string& data); bool write_blob(const char* data, int length); bool write_blob(const std::string& data); + bool write_struct(void* data, int length); bool write_array_header(unsigned char type, unsigned int element_count, unsigned int element_size); diff --git a/src/client/game/demonware/data_types.hpp b/src/client/game/demonware/data_types.hpp index 45eaa11d..74698bf0 100644 --- a/src/client/game/demonware/data_types.hpp +++ b/src/client/game/demonware/data_types.hpp @@ -246,10 +246,10 @@ namespace demonware class bdContextUserStorageFileInfo final : public bdTaskResult { public: - std::uint32_t create_time; - std::uint32_t modifed_time; + uint32_t create_time; + uint32_t modifed_time; bool priv; - std::uint64_t owner_id; + uint64_t owner_id; std::string account_type; std::string filename; @@ -277,7 +277,7 @@ namespace demonware class bdPublicProfileInfo final : public bdTaskResult { public: - std::uint64_t m_entityID; + uint64_t m_entityID; std::string m_memberplayer_card; void serialize(byte_buffer* buffer) override @@ -346,18 +346,87 @@ namespace demonware { public: uint64_t user_id; - int64_t performance; + float performance; void serialize(byte_buffer* buffer) override { buffer->write_uint64(this->user_id); - buffer->write_int64(this->performance); + buffer->write_float(this->performance); } void deserialize(byte_buffer* buffer) override { buffer->read_uint64(&this->user_id); - buffer->read_int64(&this->performance); + buffer->read_float(&this->performance); + } + }; + + class bdRewardEvent final : public bdTaskResult + { + public: + uint32_t push_type; + unsigned char r2; + uint64_t user_id; + std::string platform1; + std::string platform2; + int32_t rewardEventType; + uint32_t r7; + int32_t r8; + std::string json_buffer; + + void serialize(byte_buffer* data) override + { + data->write_uint32(push_type); + data->write_ubyte(r2); + data->write_uint64(user_id); + data->write_string(platform1); + data->write_string(platform2); + data->write_int32(rewardEventType); + data->write_uint32(r7); + data->write_int32(r8); + data->write_string(json_buffer); + } + }; + + class bdMarketplaceInventory final : public bdTaskResult + { + public: + uint64_t m_playerId; + std::string unk; + uint32_t m_itemId; + uint32_t m_itemQuantity; + uint32_t m_itemXp; + std::string m_itemData; + uint32_t m_expireDateTime; + int64_t m_expiryDuration; + uint16_t m_collisionField; + uint32_t m_modDateTime; + + void serialize(byte_buffer* data) override + { + data->write_uint64(m_playerId); + data->write_string(unk); + data->write_uint32(m_itemId); + data->write_uint32(m_itemQuantity); + data->write_uint32(m_itemXp); + data->write_blob(m_itemData); + data->write_uint32(m_expireDateTime); + data->write_int64(m_expiryDuration); + data->write_uint16(m_collisionField); + data->write_uint32(m_modDateTime); + } + }; + + class bdMarketplaceCurrency final : public bdTaskResult + { + public: + std::uint8_t m_currencyId; + std::uint32_t m_value; + + void serialize(byte_buffer* data) override + { + data->write_ubyte(m_currencyId); + data->write_uint32(m_value); } }; } diff --git a/src/client/game/demonware/dw_include.hpp b/src/client/game/demonware/dw_include.hpp index 2df78a13..f631d417 100644 --- a/src/client/game/demonware/dw_include.hpp +++ b/src/client/game/demonware/dw_include.hpp @@ -1,6 +1,6 @@ #pragma once -//#define DW_DEBUG +#define DW_DEBUG #include "game/types/demonware.hpp" using namespace game::demonware; @@ -13,4 +13,6 @@ using namespace game::demonware; #include "reply.hpp" #include "service.hpp" +#include "loot/loot.hpp" + #include "services.hpp" \ No newline at end of file diff --git a/src/client/game/demonware/loot/loot.cpp b/src/client/game/demonware/loot/loot.cpp new file mode 100644 index 00000000..8dde5c82 --- /dev/null +++ b/src/client/game/demonware/loot/loot.cpp @@ -0,0 +1,621 @@ +#include +#include "../dw_include.hpp" + +#include "game/game.hpp" + +#include "utils/io.hpp" +#include "utils/json.hpp" + +namespace demonware +{ + namespace loot + { + const std::string& json_data_path = "iw7-mod/players2/user/loot/loot.json"; + nlohmann::json json_buffer; + + namespace csv + { + struct LootCsv + { + std::string file; + std::uint32_t index; + std::uint32_t quality; + std::uint32_t salvageReturned; + std::uint32_t cost; + }; + + struct LootCrateCsv + { + std::string file; + std::uint32_t index; + std::uint32_t cost; + std::uint32_t premiumCost; + std::uint32_t CODPointsSKU; + }; + + LootCsv weapon + { + .file = "mp/loot/iw7_weapon_loot_master.csv", + .index = 0, + .quality = 6, + .salvageReturned = 3, + .cost = 4, + }; + + LootCsv killstreak + { + .file = "mp/loot/iw7_killstreak_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 3, + .cost = 4, + }; + + LootCsv consumable + { + .file = "mp/loot/iw7_consumable_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_attachments + { + .file = "mp/loot/iw7_cosmetic_attachments_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_calling_cards + { + .file = "mp/loot/iw7_cosmetic_calling_cards_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_camos + { + .file = "mp/loot/iw7_cosmetic_camos_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_emblems + { + .file = "mp/loot/iw7_cosmetic_emblems_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_emotes + { + .file = "mp/loot/iw7_cosmetic_emotes_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_heroes + { + .file = "mp/loot/iw7_cosmetic_heroes_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_reticles + { + .file = "mp/loot/iw7_cosmetic_reticles_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv cosmetic_rigs + { + .file = "mp/loot/iw7_cosmetic_rigs_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 4, + .cost = 5, + }; + + LootCsv zombiefatefortune + { + .file = "cp/loot/iw7_zombiefatefortune_loot_master.csv", + .index = 0, + .quality = 2, + .salvageReturned = 0xFF, + .cost = 0xFF, + }; + + LootCrateCsv loot_crate + { + .file = "mp/loot/iw7_loot_crate_loot_master.csv", + .index = 0, + .cost = 3, + .premiumCost = 4, + .CODPointsSKU = 5, + }; + } + + std::vector lootmap_weapon; + std::vector lootmap_killstreak; + std::vector lootmap_consumable; + std::vector lootmap_cosmetic_attachments; + std::vector lootmap_cosmetic_calling_cards; + std::vector lootmap_cosmetic_camos; + std::vector lootmap_cosmetic_emblems; + std::vector lootmap_cosmetic_emotes; + std::vector lootmap_cosmetic_heroes; + std::vector lootmap_cosmetic_reticles; + std::vector lootmap_cosmetic_rigs; + std::vector lootmap_zombiefatefortune; + + std::unordered_map m_lootmap; + + struct LootCrate + { + std::uint32_t cost; + std::uint32_t premiumCost; + std::uint32_t salvageCost; + }; + + std::unordered_map lootcrates; + + void read_loot_csv(csv::LootCsv& csv, std::vector& lootmap) + { + lootmap.clear(); + + const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_STRINGTABLE, csv.file.data(), 0).stringTable; + + for (auto row = 0; row < asset->rowCount; row++) + { + const std::uint32_t id = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv.index)); + const std::uint32_t quality = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv.quality)); + + std::uint32_t salvageReturned = 0; + std::uint32_t cost = 0; + if (csv.salvageReturned != 0xFF) + { + salvageReturned = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv.salvageReturned)); + } + if (csv.cost != 0xFF) + { + cost = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv.cost)); + } + + const Item loot = { id, quality, salvageReturned, cost }; + m_lootmap[id] = loot; + lootmap.push_back(id); + } + } + + void read_weapon_csv() + { + read_loot_csv(csv::weapon, lootmap_weapon); + } + + void read_killstreak_csv() + { + read_loot_csv(csv::killstreak, lootmap_killstreak); + } + + void read_consumable_csv() + { + read_loot_csv(csv::consumable, lootmap_consumable); + } + + void read_cosmetic_attachments_csv() + { + read_loot_csv(csv::cosmetic_attachments, lootmap_cosmetic_attachments); + } + + void read_cosmetic_calling_cards_csv() + { + read_loot_csv(csv::cosmetic_calling_cards, lootmap_cosmetic_calling_cards); + } + + void read_cosmetic_camos_csv() + { + read_loot_csv(csv::cosmetic_camos, lootmap_cosmetic_camos); + } + + void read_cosmetic_emblems_csv() + { + read_loot_csv(csv::cosmetic_emblems, lootmap_cosmetic_emblems); + } + + void read_cosmetic_emotes_csv() + { + read_loot_csv(csv::cosmetic_emotes, lootmap_cosmetic_emotes); + } + + void read_cosmetic_heroes_csv() + { + read_loot_csv(csv::cosmetic_heroes, lootmap_cosmetic_heroes); + } + + void read_cosmetic_reticles_csv() + { + read_loot_csv(csv::cosmetic_reticles, lootmap_cosmetic_reticles); + } + + void read_cosmetic_rigs_csv() + { + read_loot_csv(csv::cosmetic_rigs, lootmap_cosmetic_rigs); + } + + void read_zombiefatefortune_csv() + { + read_loot_csv(csv::zombiefatefortune, lootmap_zombiefatefortune); + } + + void read_loot_crate_csv() + { + lootcrates.clear(); + + const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_STRINGTABLE, csv::loot_crate.file.data(), 0).stringTable; + + for (auto row = 0; row < asset->rowCount; row++) + { + const std::uint32_t id = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv::loot_crate.index)); + const std::uint32_t cost = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv::loot_crate.cost)); + const std::uint32_t premiumCost = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv::loot_crate.premiumCost)); + const std::uint32_t CODPointsSKU = std::atoi(game::StringTable_GetColumnValueForRow(asset, row, csv::loot_crate.CODPointsSKU)); + lootcrates[id] = { cost, premiumCost, CODPointsSKU }; + } + } + + void cache_loot() + { + static bool once = false; + if (once) return; + once = true; + + m_lootmap.clear(); + + // mp + read_weapon_csv(); + read_killstreak_csv(); + read_consumable_csv(); + read_cosmetic_attachments_csv(); + read_cosmetic_calling_cards_csv(); + read_cosmetic_camos_csv(); + read_cosmetic_emblems_csv(); + read_cosmetic_emotes_csv(); + read_cosmetic_heroes_csv(); + read_cosmetic_reticles_csv(); + read_cosmetic_rigs_csv(); + + // cp + read_zombiefatefortune_csv(); + + // crates + read_loot_crate_csv(); + } + + // Brought to you by ChatGPT + std::vector get_random_loot_from_map(std::vector& lootmap, const size_t itemAmount, const float luckFactor, std::uint32_t quaranteedQuality = 0) + { + // Edge case: if the vector is empty + if (lootmap.empty()) { + throw std::runtime_error("Lootmap is empty"); + } + + // Edge case: if itemAmount is more than the number of available items + if (itemAmount > lootmap.size()) { + throw std::runtime_error("Item amount exceeds the number of available items"); + } + + // Validate the luckFactor (should be positive) + if (luckFactor <= 0) { + throw std::invalid_argument("Luck factor must be greater than zero"); + } + + // Calculate the total weight based on adjusted rarity values + double totalWeight = 0; + std::vector adjustedWeights(lootmap.size()); + + for (size_t i = 0; i < lootmap.size(); ++i) { + if (!get_loot(lootmap[i]).quality) continue; + + // Calculate weight as inverse of rarity, adjusted by luckFactor + adjustedWeights[i] = 1.0 / std::pow(get_loot(lootmap[i]).quality, luckFactor); + totalWeight += adjustedWeights[i]; + } + + assert(adjustedWeights.size() >= itemAmount); + + // Set up the random number generator + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0.0, totalWeight); + + std::vector selectedItems; + + // Select items + while (selectedItems.size() < itemAmount) { + double randomValue = dis(gen); + double cumulativeWeight = 0; + + // Find the item corresponding to the random value + for (size_t i = 0; i < lootmap.size(); ++i) { + cumulativeWeight += adjustedWeights[i]; + if (randomValue < cumulativeWeight) { + if (quaranteedQuality && get_loot(lootmap[i]).quality < quaranteedQuality) + continue; + else + quaranteedQuality = 0; + + // Add item to the result if it's not already selected + if (std::find(selectedItems.begin(), selectedItems.end(), get_loot(lootmap[i])) == selectedItems.end()) { + selectedItems.push_back(get_loot(lootmap[i])); + break; + } + } + } + } + + return selectedItems; + } + + std::vector combined_mp_lootmaps; + std::vector combine_mp_lootmaps() + { + std::vector& lootmap = combined_mp_lootmaps; + if (!combined_mp_lootmaps.empty()) + { + return combined_mp_lootmaps; + } + + lootmap.insert(lootmap.end(), lootmap_weapon.begin(), lootmap_weapon.end()); + lootmap.insert(lootmap.end(), lootmap_killstreak.begin(), lootmap_killstreak.end()); + lootmap.insert(lootmap.end(), lootmap_consumable.begin(), lootmap_consumable.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_attachments.begin(), lootmap_cosmetic_attachments.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_calling_cards.begin(), lootmap_cosmetic_calling_cards.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_camos.begin(), lootmap_cosmetic_camos.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_emblems.begin(), lootmap_cosmetic_emblems.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_emotes.begin(), lootmap_cosmetic_emotes.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_heroes.begin(), lootmap_cosmetic_heroes.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_reticles.begin(), lootmap_cosmetic_reticles.end()); + lootmap.insert(lootmap.end(), lootmap_cosmetic_rigs.begin(), lootmap_cosmetic_rigs.end()); + return lootmap; + } + + std::vector get_all_lootmaps() + { + cache_loot(); + + std::vector lootmap = combine_mp_lootmaps(); + lootmap.insert(lootmap.end(), lootmap_zombiefatefortune.begin(), lootmap_zombiefatefortune.end()); + return lootmap; + } + + std::vector all_loot; + std::vector get_all_loot() + { + if (!all_loot.empty()) + { + return all_loot; + } + + std::vector& items = all_loot; + auto lootmap = get_all_lootmaps(); + for (size_t i = 0; i < lootmap.size(); i++) + { + items.push_back(get_loot(lootmap[i])); + } + + return items; + }; + + std::vector get_all_loot_owned() + { + auto lootmap = get_all_lootmaps(); + std::vector items{}; + for (size_t i = 0; i < lootmap.size(); i++) + { + if (get_item_balance(lootmap[i])) + { + items.push_back(get_loot(lootmap[i])); + } + } + + return items; + } + + Item get_loot(const std::uint32_t item_id) + { + if (m_lootmap.contains(item_id)) + { + return m_lootmap[item_id]; + } + + return {}; + } + + std::vector get_random_loot_CommonCrate(const float scale = 1.0f, std::uint32_t quality = 0) + { + std::vector lootmap = combine_mp_lootmaps(); + + return get_random_loot_from_map(lootmap, 3, scale, quality); + } + + std::vector get_random_loot_RareCrate() + { + return get_random_loot_CommonCrate(0.34f, 2); + } + + std::vector get_random_loot_ZombieCrate(const float scale = 1.0f, std::uint32_t quality = 0) + { + std::vector lootmap = combine_mp_lootmaps(); + + auto mp = get_random_loot_from_map(lootmap, 2, scale); + auto cp = get_random_loot_from_map(lootmap_zombiefatefortune, 1, scale); + + std::vector items; + items.push_back(mp[0]); + items.push_back(mp[1]); + items.push_back(cp[0]); + + return items; + } + + std::vector get_random_loot_ZombieRareCrate() + { + return get_random_loot_ZombieCrate(0.34f, 2); + } + + std::vector get_random_loot_ZombieCardPack(const float scale = 1.0f, std::uint32_t quality = 0) + { + return get_random_loot_from_map(lootmap_zombiefatefortune, 3, scale, quality); + } + + std::vector get_random_loot_ZombieRareCardPack() + { + return get_random_loot_ZombieCardPack(0.34f, 2); + } + + std::vector get_random_loot(const std::uint32_t lootbox_id) + { + cache_loot(); + + switch (lootbox_id) + { + case 70000: // CommonCrate + return get_random_loot_CommonCrate(); + case 70001: // RareCrate + return get_random_loot_RareCrate(); + case 70002: // ZombieCrate + return get_random_loot_ZombieCrate(); + case 70003: // ZombieRareCrate + return get_random_loot_ZombieRareCrate(); + case 70004: // ZombieCardPack + return get_random_loot_ZombieCardPack(); + case 70005: // ZombieRareCardPack + return get_random_loot_ZombieRareCardPack(); + default: + printf("[DW]: Missing LootCrate logic for %d, using CommonCrate\n", lootbox_id); + return {}; + } + } + + std::uint32_t get_lootcrate_cost(const std::uint32_t id, const std::uint32_t type) + { + if (lootcrates.contains(id)) + { + switch(type) + { + case CurrencyType::keys: + return lootcrates[id].cost; + break; + case CurrencyType::codpoints: + return lootcrates[id].premiumCost; + break; + case CurrencyType::salvage: + return lootcrates[id].salvageCost; + break; + } + } + + return 0; + } + + template void json_put(nlohmann::json& field, T value) + { + field = value; + } + + template void json_add(nlohmann::json& field, T value) + { + if (!field.is_null()) + { + field = field.get(); + } + else + { + field = value; + } + } + + template T json_read(nlohmann::json& field) + { + if (!field.is_null()) + { + return field.get(); + } + else + { + return T{}; + } + } + + void save_json_data() + { + auto dump = json_buffer.dump(4); + utils::io::write_file(json_data_path, dump); + } + + void read_json_data() + { + if (!json_buffer.empty()) + { + return; + } + + if (utils::io::file_exists(json_data_path)) + { + auto data = utils::io::read_file(json_data_path); + json_buffer = nlohmann::json::parse(data); + return; + } + + json_buffer = {}; + + set_currency_balance(CurrencyType::keys, 9999999); + set_currency_balance(CurrencyType::salvage, 9999); + set_currency_balance(CurrencyType::codpoints, 99999); + save_json_data(); + } + + void set_item_balance(const std::uint32_t item_id, const std::uint32_t amount) + { + json_put(json_buffer["Loot"][std::to_string(item_id)]["Balance"], amount); + } + + std::uint32_t get_item_balance(const std::uint32_t item_id) + { + read_json_data(); + return json_read(json_buffer["Loot"][std::to_string(item_id)]["Balance"]); + } + + void set_currency_balance(const std::uint32_t currency_id, const std::uint32_t amount) + { + json_put(json_buffer["Currency"][std::to_string(currency_id)]["Balance"], amount); + } + + std::uint32_t get_currency_balance(const std::uint32_t currency_id) + { + read_json_data(); + return json_read(json_buffer["Currency"][std::to_string(currency_id)]["Balance"]); + } + + void save() + { + save_json_data(); + } + } +} \ No newline at end of file diff --git a/src/client/game/demonware/loot/loot.hpp b/src/client/game/demonware/loot/loot.hpp new file mode 100644 index 00000000..a373bf57 --- /dev/null +++ b/src/client/game/demonware/loot/loot.hpp @@ -0,0 +1,42 @@ +#pragma once + +namespace demonware +{ + namespace loot + { + struct Item + { + std::uint32_t id; + std::uint32_t quality; + std::uint32_t salvageReturned; + std::uint32_t cost; + + // Define equality operator for Item + bool operator==(const Item& other) const { + return id == other.id && quality == other.quality && salvageReturned == other.salvageReturned && cost == other.cost; + } + }; + + enum CurrencyType + { + keys = 11, + salvage = 12, + codpoints = 20, + }; + + std::vector get_random_loot(const std::uint32_t lootbox_id); + std::vector get_all_loot(); + std::vector get_all_loot_owned(); + Item get_loot(const std::uint32_t item_id); + + std::uint32_t get_lootcrate_cost(const std::uint32_t id, const std::uint32_t type); + + void set_item_balance(const std::uint32_t item_id, const std::uint32_t amount); + std::uint32_t get_item_balance(const std::uint32_t item_id); + + void set_currency_balance(const std::uint32_t currency_id, const std::uint32_t amount); + std::uint32_t get_currency_balance(const std::uint32_t currency_id); + + void save(); + } +} diff --git a/src/client/game/demonware/reply.cpp b/src/client/game/demonware/reply.cpp index afaf5645..383fcfce 100644 --- a/src/client/game/demonware/reply.cpp +++ b/src/client/game/demonware/reply.cpp @@ -5,6 +5,8 @@ namespace demonware { + uint64_t service_reply::transaction_id = 0; + std::string unencrypted_reply::data() { byte_buffer result; diff --git a/src/client/game/demonware/reply.hpp b/src/client/game/demonware/reply.hpp index 246bda21..2041d66b 100644 --- a/src/client/game/demonware/reply.hpp +++ b/src/client/game/demonware/reply.hpp @@ -99,10 +99,11 @@ namespace demonware { } + static uint64_t transaction_id; + uint64_t send() { - static uint64_t id = 0x0000000000000000; - const auto transaction_id = ++id; + transaction_id++; byte_buffer buffer; buffer.write_uint64(transaction_id); @@ -133,6 +134,36 @@ namespace demonware return transaction_id; } + uint64_t send_struct() + { + transaction_id++; + + byte_buffer buffer; + buffer.write_uint64(transaction_id); + buffer.write_uint32(this->error_); + buffer.write_ubyte(this->type_); + + if (!this->error_) + { + if (!this->objects_.empty()) + { + for (auto& object : this->objects_) + { + object->serialize(&buffer); + } + + this->objects_.clear(); + } + } + else + { + buffer.write_uint64(transaction_id); + } + + this->reply_.send(&buffer, true); + return transaction_id; + } + template void add(std::unique_ptr& object) { @@ -140,6 +171,11 @@ namespace demonware this->objects_.emplace_back(std::move(object)); } + void set_error(uint32_t err) + { + this->error_ = err; + } + private: uint8_t type_; uint32_t error_; diff --git a/src/client/game/demonware/service.hpp b/src/client/game/demonware/service.hpp index 157911f3..e196864b 100644 --- a/src/client/game/demonware/service.hpp +++ b/src/client/game/demonware/service.hpp @@ -53,7 +53,7 @@ namespace demonware if (it != this->tasks_.end()) { #ifdef DW_DEBUG - printf("[DW] %s: executing task '%d'\n", name_.data(), this->task_id_); + printf("[DW] %s: executing task '%d' (transaction ID: %llu)\n", name_.data(), this->task_id_, service_reply::transaction_id + 1); #endif it->second(server, &buffer); diff --git a/src/client/game/demonware/services/bdMarketingComms.cpp b/src/client/game/demonware/services/bdMarketingComms.cpp index 9ac5f988..60bd043f 100644 --- a/src/client/game/demonware/services/bdMarketingComms.cpp +++ b/src/client/game/demonware/services/bdMarketingComms.cpp @@ -7,28 +7,65 @@ namespace demonware { bdMarketingComms::bdMarketingComms() : service(104, "bdMarketingComms") { - this->register_task(1, &bdMarketingComms::getMessages); this->register_task(4, &bdMarketingComms::reportFullMessagesViewed); - this->register_task(6, &bdMarketingComms::unk6); + this->register_task(6, &bdMarketingComms::getMessages); } - void bdMarketingComms::getMessages(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketingComms::reportFullMessagesViewed(service_server* server, byte_buffer* buffer) const { // TODO: auto reply = server->create_reply(this->task_id()); reply.send(); } - void bdMarketingComms::reportFullMessagesViewed(service_server* server, byte_buffer* /*buffer*/) const +#pragma pack(push, 1) + struct bdCommsGetMessagesRequest { - // TODO: - auto reply = server->create_reply(this->task_id()); - reply.send(); - } + char __pad0[23]; + }; static_assert(sizeof(bdCommsGetMessagesRequest) == 23); - void bdMarketingComms::unk6(service_server* server, byte_buffer* /*buffer*/) const + struct unk_s { - // TODO: - server->create_reply(this->task_id(), BD_NO_FILE).send(); + char __pad0[17]; + unsigned char unk; + }; + + struct bdCommsGetMessagesResponse + { + unk_s unk[1]; + }; //static_assert(sizeof(bdCommsGetMessagesResponse) == 180); +#pragma pack(pop) + + void bdMarketingComms::getMessages(service_server* server, byte_buffer* buffer) const + { + bdCommsGetMessagesRequest request{}; + + class bdCommsGetMessagesResult final : public bdTaskResult + { + public: + bdCommsGetMessagesResponse response; + + void serialize(byte_buffer* data) override + { + data->write_struct(&response, sizeof(bdCommsGetMessagesResponse)); + } + }; + + //buffer->read_struct(&request, sizeof(bdCommsGetMessagesRequest)); + + auto reply = server->create_reply(this->task_id()); + + auto info = std::make_unique(); + + unsigned char unk[18]{ 0x0A, 0x10, 0x08, 0x00, 0x12, 0x00, 0x1A, 0x00, 0x22, 0x00, 0x2A, 0x00, 0x32, 0x00, 0x38, 0x00, 0x40, 0x01 }; + for (auto i = 0; i < 1; i++) + { + memcpy(info->response.unk[i].__pad0, unk, 17); + } + info->response.unk[0].unk = 0x01; + + reply.add(info); + + reply.send_struct(); } } diff --git a/src/client/game/demonware/services/bdMarketingComms.hpp b/src/client/game/demonware/services/bdMarketingComms.hpp index 63c549c2..ce3d6fc9 100644 --- a/src/client/game/demonware/services/bdMarketingComms.hpp +++ b/src/client/game/demonware/services/bdMarketingComms.hpp @@ -8,8 +8,7 @@ namespace demonware bdMarketingComms(); private: - void getMessages(service_server* server, byte_buffer* buffer) const; void reportFullMessagesViewed(service_server* server, byte_buffer* buffer) const; - void unk6(service_server* server, byte_buffer* buffer) const; + void getMessages(service_server* server, byte_buffer* buffer) const; }; } diff --git a/src/client/game/demonware/services/bdMarketplace.cpp b/src/client/game/demonware/services/bdMarketplace.cpp index fb7a98b3..001518fa 100644 --- a/src/client/game/demonware/services/bdMarketplace.cpp +++ b/src/client/game/demonware/services/bdMarketplace.cpp @@ -3,6 +3,10 @@ #include +#include "steam/steam.hpp" + +#include "utils/json.hpp" + namespace demonware { bdMarketplace::bdMarketplace() : service(80, "bdMarketplace") @@ -14,26 +18,72 @@ namespace demonware this->register_task(58, &bdMarketplace::validateInventoryItemsToken); this->register_task(60, &bdMarketplace::steamProcessDurable); this->register_task(85, &bdMarketplace::steamProcessDurableV2); - this->register_task(122, &bdMarketplace::purchaseSkus); + this->register_task(106, &bdMarketplace::purchaseSkus); this->register_task(130, &bdMarketplace::getBalance); this->register_task(132, &bdMarketplace::getBalanceV2); this->register_task(165, &bdMarketplace::getInventoryPaginated); this->register_task(193, &bdMarketplace::putPlayersInventoryItems); + this->register_task(199, &bdMarketplace::pawnItems); this->register_task(232, &bdMarketplace::getEntitlements); } - void bdMarketplace::startExchangeTransaction(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketplace::startExchangeTransaction(service_server* server, byte_buffer* buffer) const { - // TODO: + class bdStartExchangeTransactionResult final : public bdTaskResult + { + public: + std::string m_exchangeTransactionId; + + void serialize(byte_buffer* data) override + { + data->write_string(m_exchangeTransactionId); + } + }; + + std::string platform; + buffer->read_string(&platform); + auto reply = server->create_reply(this->task_id()); + auto info = std::make_unique(); + info->m_exchangeTransactionId = std::to_string(service_reply::transaction_id); + reply.add(info); reply.send(); } - void bdMarketplace::purchaseOnSteamInitialize(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketplace::purchaseOnSteamInitialize(service_server* server, byte_buffer* buffer) const { - // TODO: + /*class bdPurchaseInitializeResult final : public bdTaskResult + { + public: + std::uint64_t m_orderId; + + void serialize(byte_buffer* data) override + { + data->write_uint64(m_orderId); + } + }; + + std::string platform; + std::string m_exchangeTransactionId; + std::uint32_t skuId; + std::string language; + std::uint64_t userID; + std::string contextUser; + + buffer->read_string(&platform); + buffer->read_string(&m_exchangeTransactionId); + buffer->read_uint32(&skuId); + buffer->read_string(&language); + buffer->read_uint64(&userID); + buffer->read_string(&contextUser); + auto reply = server->create_reply(this->task_id()); - reply.send(); + auto info = std::make_unique(); + info->m_orderId = 0; // would need to emulate steam microtransaction stuff... + reply.add(info); + reply.send();*/ + + server->create_reply(this->task_id(), BD_MARKETPLACE_STEAM_NOT_APPROVED).send(); } void bdMarketplace::purchaseOnSteamFinalize(service_server* server, byte_buffer* /*buffer*/) const @@ -64,18 +114,133 @@ namespace demonware reply.send(); } - void bdMarketplace::steamProcessDurableV2(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketplace::steamProcessDurableV2(service_server* server, byte_buffer* buffer) const { + std::string platform; + buffer->read_string(&platform); + + std::string unk1; + buffer->read_string(&unk1); + + std::uint32_t unk2; + buffer->read_uint32(&unk2); + + std::uint64_t unk3; + buffer->read_uint64(&unk3); + + std::string unk4; + buffer->read_string(&unk4); + // TODO: auto reply = server->create_reply(this->task_id()); reply.send(); } - void bdMarketplace::purchaseSkus(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketplace::purchaseSkus(service_server* server, byte_buffer* buffer) const { - // TODO: - auto reply = server->create_reply(this->task_id()); - reply.send(); + std::string platform, clientTransactionId; + uint32_t numSkuIds; + uint32_t skuIds[4]{}; + uint32_t numQuanities; + uint32_t skuQuanities[4]{}; + uint32_t numMaxResults; + uint32_t numDiscounts; + uint32_t skuDiscounts[4]{}; + uint32_t numCouponRecipients; + // coupon data here + uint32_t numCouponMetadata; + // coupon metadata here + uint8_t customSourceType; + bool ignoreEntitlements; + + buffer->read_string(&platform); + buffer->read_string(&clientTransactionId); + + buffer->read_uint32(&numSkuIds); + assert(numSkuIds < 4); + + for (auto i = 0u; i < numSkuIds; i++) + { + buffer->read_uint32(&skuIds[i]); + } + + buffer->read_uint32(&numQuanities); + assert(numQuanities < 4); + + for (auto i = 0u; i < numQuanities; i++) + { + buffer->read_uint32(&skuQuanities[i]); + } + + buffer->read_uint32(&numMaxResults); + + buffer->read_uint32(&numDiscounts); + assert(numDiscounts < 4); + + for (auto i = 0u; i < numDiscounts; i++) + { + buffer->read_uint32(&skuDiscounts[i]); + } + + buffer->read_uint32(&numCouponRecipients); + assert(numCouponRecipients == 0); + + buffer->read_uint32(&numCouponMetadata); + assert(numCouponMetadata == 0); + + buffer->read_ubyte(&customSourceType); + buffer->read_bool(&ignoreEntitlements); + + assert(numSkuIds == 1); + + auto id = skuIds[0]; + auto num = skuQuanities[0]; + + // schematic buy comes through here + if (id < 70000 || id > 75223) + { + const auto item = loot::get_loot(id); + const auto cost = item.cost; + const auto item_balance = loot::get_item_balance(id); + const auto currency_balance = loot::get_currency_balance(loot::CurrencyType::salvage); + + if (cost > currency_balance) + { + server->create_reply(this->task_id(), BD_MARKETPLACE_ERROR).send(); + return; + } + + loot::set_currency_balance(loot::CurrencyType::salvage, currency_balance - cost); + loot::set_item_balance(id, item_balance + num); + + loot::save(); + + server->create_reply(this->task_id(), BD_NO_ERROR).send(); + return; + } + + int currency_id = loot::CurrencyType::keys; + if (id > 70000 && (id - 5000) >= 70000) + { + id = id - 5000; + currency_id = loot::CurrencyType::codpoints; + } + + const auto current_balance = loot::get_currency_balance(currency_id); + const auto cost = loot::get_lootcrate_cost(id, currency_id); + + if (cost > current_balance) + { + server->create_reply(this->task_id(), BD_MARKETPLACE_ERROR).send(); + return; + } + + loot::set_currency_balance(currency_id, current_balance - cost); + loot::set_item_balance(id, loot::get_item_balance(id) + num); + + loot::save(); + + server->create_reply(this->task_id(), BD_NO_ERROR).send(); } void bdMarketplace::getBalance(service_server* server, byte_buffer* /*buffer*/) const @@ -85,17 +250,83 @@ namespace demonware reply.send(); } - void bdMarketplace::getBalanceV2(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketplace::getBalanceV2(service_server* server, byte_buffer* buffer) const { - // TODO: + std::string platform; + buffer->read_string(&platform); + + std::uint32_t maxNumResults; + buffer->read_uint32(&maxNumResults); + auto reply = server->create_reply(this->task_id()); + + std::uint32_t numResults = 0; + + auto add_result = [&](std::uint8_t id, std::uint32_t value) + { + if (numResults >= maxNumResults) return; + auto info = std::make_unique(); + info->m_currencyId = id; + info->m_value = value; + reply.add(info); + numResults++; + }; + + add_result(loot::CurrencyType::keys, loot::get_currency_balance(loot::CurrencyType::keys)); + add_result(loot::CurrencyType::salvage, loot::get_currency_balance(loot::CurrencyType::salvage)); + add_result(loot::CurrencyType::codpoints, loot::get_currency_balance(loot::CurrencyType::codpoints)); + reply.send(); } - void bdMarketplace::getInventoryPaginated(service_server* server, byte_buffer* /*buffer*/) const + void bdMarketplace::getInventoryPaginated(service_server* server, byte_buffer* buffer) const { - // TODO: + std::string platform; + std::uint32_t itemsPerPage, pageNum; + + buffer->read_string(&platform); + buffer->read_uint32(&pageNum); + buffer->read_uint32(&itemsPerPage); + + assert(itemsPerPage); + + static unsigned int paginated_index = 0; + auto reply = server->create_reply(this->task_id()); + + auto all_loot = loot::get_all_loot_owned(); + + const unsigned int index = paginated_index; + for (size_t i = 0; i < all_loot.size() - index; i++) + { + const auto loot = all_loot[paginated_index]; + const auto balance = loot::get_item_balance(loot.id); + assert(balance); + + auto info = std::make_unique(); + + info->m_playerId = steam::SteamUser()->GetSteamID().bits; + info->unk = ""; + info->m_itemId = loot.id; + info->m_itemQuantity = balance; + info->m_itemXp = 0; + info->m_itemData = ""; + info->m_expireDateTime = 0; + info->m_expiryDuration = 0; + info->m_collisionField = 0; + info->m_modDateTime = 0; + + reply.add(info); + + paginated_index++; + if (i >= itemsPerPage - 1) + { + reply.send(); + return; + } + } + + paginated_index = 0; reply.send(); } @@ -106,6 +337,48 @@ namespace demonware reply.send(); } + void bdMarketplace::pawnItems(service_server* server, byte_buffer* buffer) const + { + std::string platform, clientTransactionId; + std::uint32_t numItemsToPawn; + + buffer->read_string(&platform); + buffer->read_string(&clientTransactionId); + + auto currency = loot::get_currency_balance(loot::CurrencyType::salvage); + + buffer->read_uint32(&numItemsToPawn); + for (auto i = 0u; i < numItemsToPawn; i++) + { + std::uint32_t id, nextBalance; + std::uint16_t unk2; // collision? + + buffer->read_uint32(&id); + buffer->read_uint32(&nextBalance); + buffer->read_uint16(&unk2); + + const auto loot = loot::get_loot(id); + const auto amount = loot::get_item_balance(loot.id); + + if (amount == 1) // why are you trying to pawn when we only have 1... + { + continue; + } + + loot::set_item_balance(loot.id, nextBalance); + currency += loot.salvageReturned; + +#ifdef DW_DEBUG + printf("[DW]: pawning %d for %d salvage\n", loot.id, loot.salvageReturned); +#endif + } + loot::set_currency_balance(loot::CurrencyType::salvage, currency); + loot::save(); + + auto reply = server->create_reply(this->task_id()); + reply.send(); + } + void bdMarketplace::getEntitlements(service_server* server, byte_buffer* /*buffer*/) const { // TODO: diff --git a/src/client/game/demonware/services/bdMarketplace.hpp b/src/client/game/demonware/services/bdMarketplace.hpp index ad8ca8c6..3a7a4c56 100644 --- a/src/client/game/demonware/services/bdMarketplace.hpp +++ b/src/client/game/demonware/services/bdMarketplace.hpp @@ -20,6 +20,7 @@ namespace demonware void getBalanceV2(service_server* server, byte_buffer* buffer) const; void getInventoryPaginated(service_server* server, byte_buffer* buffer) const; void putPlayersInventoryItems(service_server* server, byte_buffer* buffer) const; + void pawnItems(service_server* server, byte_buffer* buffer) const; void getEntitlements(service_server* server, byte_buffer* buffer) const; }; } diff --git a/src/client/game/demonware/services/bdMatchMaking.cpp b/src/client/game/demonware/services/bdMatchMaking.cpp index 331e25a1..c7122ffc 100644 --- a/src/client/game/demonware/services/bdMatchMaking.cpp +++ b/src/client/game/demonware/services/bdMatchMaking.cpp @@ -71,7 +71,7 @@ namespace demonware { auto result = std::make_unique(); result->user_id = steam::SteamUser()->GetSteamID().bits; - result->performance = 10; + result->performance = 10.0f; auto reply = server->create_reply(this->task_id()); reply.add(result); diff --git a/src/client/game/demonware/services/bdReward.cpp b/src/client/game/demonware/services/bdReward.cpp index e1592597..6fe9a482 100644 --- a/src/client/game/demonware/services/bdReward.cpp +++ b/src/client/game/demonware/services/bdReward.cpp @@ -1,6 +1,13 @@ #include #include "../dw_include.hpp" +#include "steam/steam.hpp" + +#include "utils/json.hpp" +#include "../loot/loot.hpp" + +//#define VERIFY_CRATE_AMOUNT + namespace demonware { bdReward::bdReward() : service(139, "bdReward") @@ -32,10 +39,200 @@ namespace demonware reply.send(); } - void bdReward::reportRewardEvents(service_server* server, byte_buffer* /*buffer*/) const + void bdReward::reportRewardEvents(service_server* server, byte_buffer* buffer) const { - // TODO: - auto reply = server->create_reply(this->task_id()); - reply.send(); + std::string platform; + unsigned short numEvents; + std::int32_t rewardEventType; + + std::string json_buffer; + + buffer->read_string(&platform); + buffer->read_uint16(&numEvents); + buffer->read_int32(&rewardEventType); // none 0, json 1 + + assert(numEvents == 1); + + buffer->read_string(&json_buffer); + + const auto send = [&](nlohmann::json& json_reply) + { + auto result = std::make_unique(); + + result->push_type = BD_REWARD_EVENT_MESSAGE; + result->r2 = 1; + result->user_id = steam::SteamUser()->GetSteamID().bits; + result->platform1 = "steam"; + result->platform2 = platform; + result->rewardEventType = rewardEventType; + result->r7 = 1; + result->r8 = 1; + result->json_buffer = json_reply.dump(0); + + byte_buffer reply_buffer; + result->serialize(&reply_buffer); + + auto reply = server->create_message(BD_LOBBY_SERVICE_PUSH_MESSAGE); + reply.send(&reply_buffer, true); + }; + + nlohmann::json json; + json = json.parse(json_buffer); + + const auto action = json["Action"].get(); + if (action == "DailyLogin") + { +#ifdef DW_DEBUG + printf("[DW]: daily login requested...\n"); +#endif + + nlohmann::json json_reply; + json_reply["Action"] = "DailyLoginResponse"; + + json_reply["LoginDayCountSP"] = -1; + json_reply["FirstTimeTodaySP"] = false; + json_reply["LoginDayCount"] = 1; + json_reply["FirstTimeToday"] = false; + + // Packs + json_reply["BasicPacks"] = nlohmann::json::value_type::array(); + json_reply["BasicPacks"][0]["Currencies"] = nlohmann::json::value_type::object(); + json_reply["BasicPacks"][0]["Currencies"]["11"] = 0; + json_reply["BasicPacks"][0]["Currencies"]["12"] = 0; + json_reply["BasicPacks"][0]["Currencies"]["20"] = 0; + json_reply["BasicPacks"][0]["Items"] = nlohmann::json::value_type::array(); + json_reply["BasicPacks"][0]["Id"] = 200018; + json_reply["SeasonPassPacks"] = nlohmann::json::value_type::array(); + + // Extra Items + json_reply["ExtraItems"] = nlohmann::json::value_type::array(); + + // Items + json_reply["Items"] = nlohmann::json::value_type::array(); + + // mp/loot/iw7_loot_crate_loot_master.csv + json_reply["Items"][0]["ItemId"] = 70005; // ZombieRareCardPack + json_reply["Items"][0]["Collision"] = 0; // not sure what this does + json_reply["Items"][0]["Balance"] = 999; + loot::set_item_balance(70005, 999); + + // Currencies + json_reply["Currencies"] = nlohmann::json::value_type::array(); + + // Keys + json_reply["Currencies"][0]["CurrencyId"] = loot::CurrencyType::keys; + json_reply["Currencies"][0]["Balance"] = loot::get_currency_balance(loot::CurrencyType::keys); + + // Salvage + json_reply["Currencies"][1]["CurrencyId"] = loot::CurrencyType::salvage; + json_reply["Currencies"][1]["Balance"] = loot::get_currency_balance(loot::CurrencyType::salvage); + + // CodPoints + json_reply["Currencies"][2]["CurrencyId"] = loot::CurrencyType::codpoints; + json_reply["Currencies"][2]["Balance"] = loot::get_currency_balance(loot::CurrencyType::codpoints); + + json_reply["ClientTx"] = json["ClientTx"]; + + send(json_reply); + } + else if (action == "ClaimLootCrates") + { +#ifdef DW_DEBUG + printf("[DW]: supply drop open requested...\n"); +#endif + + nlohmann::json json_reply; + json_reply["Action"] = "ClaimLootCratesResponse"; + + json_reply["Packs"] = nlohmann::json::value_type::array(); + json_reply["Items"] = nlohmann::json::value_type::array(); + json_reply["Currencies"] = nlohmann::json::value_type::array(); + + // Items, Packs + + const auto rule_id = json["RuleId"].is_number_integer() ? json["RuleId"].get() : 0; + const auto crate_id = 70000 + rule_id; + + int itemidx = 0; + + auto crate_balance = loot::get_item_balance(crate_id); +#ifdef VERIFY_CRATE_AMOUNT + if (!crate_balance) + { + server->create_reply(this->task_id(), BD_MARKETPLACE_ERROR).send(); + return; + } +#else + if (!crate_balance) + { + crate_balance = 1; + } +#endif + + const auto new_crate_balance = crate_balance - 1; + + json_reply["Items"][itemidx]["ItemId"] = crate_id; + json_reply["Items"][itemidx]["Collision"] = 0; // not sure what this does + json_reply["Items"][itemidx]["Balance"] = new_crate_balance; + itemidx++; + + loot::set_item_balance(crate_id, new_crate_balance); + + auto loot = loot::get_random_loot(crate_id); + + for (auto i = 0; i < loot.size(); i++) + { + const auto item_id = loot[i].id; + + json_reply["Packs"][i] = item_id; + + const auto balance = loot::get_item_balance(item_id) + 1; + loot::set_item_balance(item_id, balance); + + json_reply["Items"][i + itemidx]["ItemId"] = item_id; + json_reply["Items"][i + itemidx]["Collision"] = 0; + json_reply["Items"][i + itemidx]["Balance"] = balance; + +#ifdef DW_DEBUG + printf("[DW]: loot %d\n", item_id); +#endif + } + + loot::save(); + + json_reply["Error"] = ""; + json_reply["ClientTx"] = json["ClientTx"]; + + send(json_reply); + } + else if (action == "StartMission") + { + printf("%s\n", json_buffer.data()); + //const auto match_id = json["MatchId"].get(); + //const auto mission_set_instance_id = json["MissionSetInstanceId"].get(); + } + else if (action == "StartMissionSet") + { + printf("%s\n", json_buffer.data()); + //const auto mission_set_id = json["MissionSetId"].get(); + } + else if (action == "EndMission") + { + printf("%s\n", json_buffer.data()); + } + else if (action == "EndMissionSet") + { + printf("%s\n", json_buffer.data()); + } + else if (action == "ResetMissions") + { + printf("%s\n", json_buffer.data()); + } + else + { + printf("[DW]: unhandled reward action \"%s\"...\n", action.data()); + } + + server->create_reply(this->task_id(), BD_NO_ERROR).send(); } } diff --git a/src/client/game/demonware/services/bdStats.cpp b/src/client/game/demonware/services/bdStats.cpp index 7d84557a..84ed498d 100644 --- a/src/client/game/demonware/services/bdStats.cpp +++ b/src/client/game/demonware/services/bdStats.cpp @@ -20,7 +20,7 @@ namespace demonware this->register_task(14, &bdStats::writeServerValidatedStats); } - void bdStats::writeStats(service_server* server, byte_buffer* /*buffer*/) const + void bdStats::writeStats(service_server* server, byte_buffer* buffer) const { // TODO: auto reply = server->create_reply(this->task_id()); @@ -104,8 +104,11 @@ namespace demonware reply.send(); } - void bdStats::writeServerValidatedStats(service_server* server, byte_buffer* /*buffer*/) const + void bdStats::writeServerValidatedStats(service_server* server, byte_buffer* buffer) const { + std::string blob; + buffer->read_blob(&blob); + // TODO: auto reply = server->create_reply(this->task_id()); reply.send(); diff --git a/src/client/game/types/demonware.hpp b/src/client/game/types/demonware.hpp index 4d1fd3af..8b2db0e1 100644 --- a/src/client/game/types/demonware.hpp +++ b/src/client/game/types/demonware.hpp @@ -21,6 +21,125 @@ namespace game::demonware DW_NET_STARTED_ONLINE = 0x6, }; + enum bdEventType + { + BD_NEW_NOTIFICATION = 0x1, + BD_FRIENDSHIP_PROPOSAL = 0x2, + BD_TEAM_PROPOSAL = 0x3, + BD_FRIEND_CONNECTED = 0x4, + BD_FRIEND_DISCONNECTED = 0x5, + BD_SESSION_INVITATION = 0x6, + BD_CHANNEL_CHAT_BROADCAST_MSG = 0x7, + BD_CHANNEL_CHAT_WHISPER_MSG = 0x8, + BD_CHANNEL_USER_SUBSCRIBED = 0x9, + BD_CHANNEL_USER_UNSUBSCRIBED = 0xA, + BD_TEAMMEMBER_CONNECTED = 0xB, + BD_TEAMMEMBER_DISCONNECTED = 0xC, + BD_FRIEND_RICH_PRESENCE_UPDATED = 0xD, + BD_FRIEND_CHAT_MSG = 0xF, + BD_TEAM_CHAT_MSG = 0x10, + BD_NOTIFY_LEAVE = 0x11, + BD_NEW_MAIL = 0x12, + BD_CHALLENGES_RECEIVED = 0x13, + BD_ASYNCHRONOUS_RESULT = 0x14, + BD_GLOBAL_INSTANT_MESSAGE = 0x15, + BD_CHANNEL_CHAT_BROADCAST_MSG_V2 = 0x16, + BD_CHANNEL_CHAT_WHISPER_MSG_V2 = 0x17, + BD_CHANNEL_USER_SUBSCRIBED_V2 = 0x18, + BD_CHANNEL_USER_UNSUBSCRIBED_V2 = 0x19, + BD_CHANNEL_USER_MUTED_V2 = 0x1A, + BD_CHANNEL_USER_PROMOTED_V2 = 0x1B, + BD_CHANNEL_USER_KICKED_V2 = 0x1C, + BD_MULTIPLE_LOGONS = 0x1D, + BD_PLAYER_BANNED = 0x1E, + BD_CHANNEL_USER_PROMOTED = 0x1F, + BD_CHANNEL_USER_KICKED = 0x20, + BD_FEATURE_BAN = 0x21, + BD_GMSG_GROUP_MESSAGE = 0x22, + BD_GMSG_BROADCAST = 0x23, + BD_TENCENT_AAS_RECORD = 0x24, + BD_NOT_WHITE_LISTED = 0x26, + BD_CHANNEL_USER_MUTED = 0x27, + BD_STABILISED = 0x28, + BD_CONSOLE_BANNED = 0x29, + BD_MULTIPLE_LINKED_ACCOUNT_LOGONS = 0x2A, + BD_LINKED_ACCOUNT_STATUS_CHANGE = 0x2B, + BD_QUEUED_MATCHING_DATA = 0x2C, + BD_EVENT_LOG_FILTERED_CATEGORIES = 0x2D, + BD_MARKETPLACE_COUPONS_GRANTED = 0x2E, + BD_TOTP_CHALLENGE = 0x2F, + BD_MARKETPLACE_ITEMS_EXPIRED = 0x30, + BD_MARKETPLACE_ITEMS_UPDATED = 0x31, + BD_MARKETPLACE_COUPONS_UPDATED = 0x32, + BD_REWARD_ACHIEVEMENT_CLAIMED = 0x33, + BD_MARKETPLACE_BALANCE_UPDATED = 0x34, + BD_MARKETPLACE_DEPOSIT_GRANTED = 0x35, + BD_MARKETPLACE_ITEMS_GRANTED = 0x36, + BD_MARKETPLACE_ENTITLEMENTS_GRANTED = 0x37, + BD_STORAGE_WEBSERVICE_WRITE = 0x38, + BD_REWARD_EVENT_MESSAGE = 0x39, + BD_MARKETING_COMMS_ASSIGNMENTS_AVAILABLE = 0x3A, + BD_PUBLISHER_VARIABLES_UPDATE_MESSAGE = 0x3B, + BD_MARKETPLACE_COUPONS_UPDATED_V2 = 0x3C, + BD_MARKETPLACE_ITEMS_GRANTED_WITH_INVENTORY_QUANTITY = 0x3D, + BD_USER_PRIVATE_PROFILE_UPDATED = 0x3E, + BD_TEAM_MEMBER_RICH_PRESENCE_UPDATED = 0x3F, + BD_TITLE_VERSION_DISABLED = 0x40, + BD_TEAM_MEMBER_USER_NAME_UPDATED = 0x41, + BD_MARKETPLACE_COUPONS_UPDATED_V3 = 0x42, + BD_REWARD_ACHIEVEMENT_MESSAGE = 0x43, + BD_PLAYER_DISCONNECTED = 0x44, + BD_CODO_TEAM_MARKETPLACE_LEVEL_CHANGED = 0x45, + BD_SUBSCRIBED_RICH_PRESENCE_UPDATED = 0x46, + BD_PLAYER_LOGON_TIME_PROHIBITED = 0x47, + BD_PLAYER_LOGON_TIME_PROHIBITED_WARNING = 0x48, + BD_MARKETPLACE_COUPONS_UPDATED_V4 = 0x49, + BD_NOT_WHITE_LISTED_WITH_MESSAGE = 0x4A, + BD_ACHIEVEMENTS_UPDATED = 0x4B, + BD_MARKETPLACE_BALANCE_UPDATED_V2 = 0x4C, + BD_MW4_CLANS_MEMBERSHIP_PROPOSAL = 0x4D, + BD_MW4_CLANS_MEMBER_ADDED = 0x4E, + BD_MW4_CLANS_PROPOSAL_REMOVED = 0x4F, + BD_MW4_CLANS_MEMBER_REMOVED = 0x50, + BD_MW4_CLANS_GROUP_DISBANDED = 0x51, + BD_MW4_CLANS_ACTIVE_GROUP_CHANGED = 0x52, + BD_MW4_CLANS_GROUP_UPDATED = 0x53, + BD_MW4_CLANS_MEMBER_UPDATED = 0x54, + BD_DEMONATA_PUSH_MESSAGE = 0xC7, + BD_DEMONATA_PROTOBUF_PUSH_MESSAGE = 0xC8, + BD_TENCENT_ANTIBOT_DATA = 0x3E9, + BD_TENCENT_ANTIBOT_PUNISH = 0x3EA, + BD_TENCENT_REWARD = 0x3EB, + BD_TENCENT_ANTIBOT_SERVER_READY = 0x3EC, + BD_TENCENT_NAME_CHANGED = 0x3ED, + BD_TENCENT_USER_LEAVE_REASON = 0x3EE, + BD_TENCENT_LOUDSPEAKER_MESSAGE = 0x3EF, + BD_TENCENT_NO_REWARD = 0x3F0, + BD_TENCENT_SECURITY_RATING = 0x3F1, + BD_QOS_HOSTS = 0x7D0, + BD_JOIN_LOBBY = 0x7D1, + BD_LOBBY_DISBANDED = 0x7D2, + BD_MATCHMAKING_SEARCH_STATUS = 0x7D3, + BD_LOBBY_NOT_FOUND = 0x7D4, + BD_CREATE_NEW_LOBBY = 0x7D5, + BD_UPDATED_LOBBY_DOCUMENT = 0x7D6, + BD_EXPECT_GAME = 0x7D7, + BD_MERGE_INTO_LOBBY = 0x7D8, + BD_JOIN_TOURNAMENT = 0x7D9, + BD_UPDATE_TOURNAMENT = 0x7DA, + BD_TOURNAMENT_FORMATION_LOBBY_DISBANDED = 0x7DB, + BD_TOURNAMENT_DISBANDED = 0x7DC, + }; + + enum bdLobbyServiceType : std::int32_t + { + BD_LOBBY_SERVICE_TASK_REPLY = 1, + BD_LOBBY_SERVICE_PUSH_MESSAGE = 2, + BD_LSG_SERVICE_ERROR = 3, + BD_LSG_SERVICE_CONNECTION = 4, + BD_LSG_SERVICE_TASK_REPLY = 5, + }; + enum bdLobbyErrorCode : std::int32_t { BD_NO_ERROR = 0x0, diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 3dfff810..2e625d68 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -74,8 +74,11 @@ #include #include #include +#include #include #include +#include +#include #include #include