From 1764ff96991570c4efc7cc25d1acf5f4127e8a4a Mon Sep 17 00:00:00 2001 From: momo5502 Date: Thu, 27 Dec 2018 17:00:46 +0100 Subject: [PATCH] Add DW emulator --- src/game/demonware/bit_buffer.cpp | 2 +- src/game/demonware/bit_buffer.hpp | 2 +- src/game/demonware/byte_buffer.cpp | 12 +- src/game/demonware/byte_buffer.hpp | 3 + src/game/demonware/i_service.hpp | 15 +- src/game/demonware/service_server.cpp | 20 +- src/game/demonware/services/bdDML.cpp | 29 ++ src/game/demonware/services/bdDML.hpp | 14 + src/game/demonware/services/bdLSGHello.cpp | 26 ++ src/game/demonware/services/bdLSGHello.hpp | 11 + src/game/demonware/services/bdSteamAuth.cpp | 60 +++ src/game/demonware/services/bdSteamAuth.hpp | 11 + src/game/demonware/services/bdStorage.cpp | 266 ++++++++++++ src/game/demonware/services/bdStorage.hpp | 28 ++ .../demonware/services/bdTitleUtilities.cpp | 21 + .../demonware/services/bdTitleUtilities.hpp | 14 + src/game/demonware/stun_server.cpp | 6 +- src/game/structs.hpp | 357 +++++++++++++++ src/module/dw.cpp | 409 +++++++++++++++++- src/module/dw.hpp | 75 ++++ src/resource.hpp | 9 + src/resource.rc | 9 + src/resources/dw/heatmap.raw | Bin 0 -> 91 bytes src/resources/dw/iotd-english.jpg | Bin 0 -> 17281 bytes src/resources/dw/iotd-english.txt | 1 + src/resources/dw/motd-english.txt | 1 + src/resources/dw/online_mp.img | Bin 0 -> 10240 bytes src/resources/dw/online_tu14_mp_english.wad | Bin 0 -> 24633 bytes src/resources/dw/playlists.aggr | Bin 0 -> 15810 bytes src/resources/dw/social_tu1.cfg | 55 +++ src/std_include.hpp | 4 + src/steam/interfaces/user.cpp | 20 +- src/utils/hook.cpp | 21 +- src/utils/hook.hpp | 3 + 34 files changed, 1470 insertions(+), 34 deletions(-) create mode 100644 src/game/demonware/services/bdDML.cpp create mode 100644 src/game/demonware/services/bdDML.hpp create mode 100644 src/game/demonware/services/bdLSGHello.cpp create mode 100644 src/game/demonware/services/bdLSGHello.hpp create mode 100644 src/game/demonware/services/bdSteamAuth.cpp create mode 100644 src/game/demonware/services/bdSteamAuth.hpp create mode 100644 src/game/demonware/services/bdStorage.cpp create mode 100644 src/game/demonware/services/bdStorage.hpp create mode 100644 src/game/demonware/services/bdTitleUtilities.cpp create mode 100644 src/game/demonware/services/bdTitleUtilities.hpp create mode 100644 src/module/dw.hpp create mode 100644 src/resources/dw/heatmap.raw create mode 100644 src/resources/dw/iotd-english.jpg create mode 100644 src/resources/dw/iotd-english.txt create mode 100644 src/resources/dw/motd-english.txt create mode 100644 src/resources/dw/online_mp.img create mode 100644 src/resources/dw/online_tu14_mp_english.wad create mode 100644 src/resources/dw/playlists.aggr create mode 100644 src/resources/dw/social_tu1.cfg diff --git a/src/game/demonware/bit_buffer.cpp b/src/game/demonware/bit_buffer.cpp index 9b2c458..808b411 100644 --- a/src/game/demonware/bit_buffer.cpp +++ b/src/game/demonware/bit_buffer.cpp @@ -18,7 +18,7 @@ namespace demonware return this->read(1, output); } - bool bit_buffer::read_u_int32(unsigned int* output) + bool bit_buffer::read_uint32(unsigned int* output) { if (!this->read_data_type(8)) { diff --git a/src/game/demonware/bit_buffer.hpp b/src/game/demonware/bit_buffer.hpp index 0de889e..f2fd5c0 100644 --- a/src/game/demonware/bit_buffer.hpp +++ b/src/game/demonware/bit_buffer.hpp @@ -13,7 +13,7 @@ namespace demonware bool read_bytes(unsigned int bytes, unsigned char* output); bool read_bool(bool* output); - bool read_u_int32(unsigned int* output); + bool read_uint32(unsigned int* output); bool read_data_type(char expected); bool write_bytes(unsigned int bytes, const char* data); diff --git a/src/game/demonware/byte_buffer.cpp b/src/game/demonware/byte_buffer.cpp index 7c6ea85..85d9dc3 100644 --- a/src/game/demonware/byte_buffer.cpp +++ b/src/game/demonware/byte_buffer.cpp @@ -122,7 +122,7 @@ namespace demonware return true; } - bool byte_buffer::read_data_type(char expected) + bool byte_buffer::read_data_type(const char expected) { if (!this->use_data_types_) return true; @@ -295,4 +295,14 @@ namespace demonware { return this->buffer_; } + + std::string byte_buffer::get_remaining() + { + return std::string(this->buffer_.begin() + this->current_byte_, this->buffer_.end()); + } + + bool byte_buffer::has_more_data() const + { + return this->buffer_.size() > this->current_byte_; + } } diff --git a/src/game/demonware/byte_buffer.hpp b/src/game/demonware/byte_buffer.hpp index 84f0fc6..43e462a 100644 --- a/src/game/demonware/byte_buffer.hpp +++ b/src/game/demonware/byte_buffer.hpp @@ -57,6 +57,9 @@ namespace demonware bool is_using_data_types() const; std::string& get_buffer(); + std::string get_remaining(); + + bool has_more_data() const; private: std::string buffer_; diff --git a/src/game/demonware/i_service.hpp b/src/game/demonware/i_service.hpp index 6784cd3..4c96ef5 100644 --- a/src/game/demonware/i_service.hpp +++ b/src/game/demonware/i_service.hpp @@ -17,7 +17,7 @@ namespace demonware i_service(const i_service&) = delete; i_service& operator=(const i_service&) = delete; - typedef std::function Callback; + typedef std::function callback; virtual uint16_t getType() = 0; @@ -42,7 +42,16 @@ namespace demonware } protected: - std::map callbacks{}; + std::map callbacks{}; + + template + void register_service(const uint8_t type, T(Class::*callback)(Args ...) const) + { + this->callbacks[type] = [this, callback](Args... args) -> T + { + return (reinterpret_cast(this)->*callback)(args...); + }; + } template void register_service(const uint8_t type, T(Class::*callback)(Args ...)) @@ -62,7 +71,7 @@ namespace demonware }; template - class i_generic_service final : public i_service + class i_generic_service : public i_service { public: uint16_t getType() override { return Type; } diff --git a/src/game/demonware/service_server.cpp b/src/game/demonware/service_server.cpp index a42f361..fe78849 100644 --- a/src/game/demonware/service_server.cpp +++ b/src/game/demonware/service_server.cpp @@ -1,5 +1,5 @@ #include -#include "service_server.hpp" +#include "module/dw.hpp" #include "utils/cryptography.hpp" namespace demonware @@ -41,7 +41,9 @@ namespace demonware result.write_int32(seed); const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast(&seed), 4)); - //result.write(utils::cryptography::des3::encrypt(data, iv, Components::DemonWare::GetKey(true))); + + const std::string key(reinterpret_cast(dw::get_key(true)), 24); + result.write(utils::cryptography::des3::encrypt(data, iv, key)); return result.get_buffer(); } @@ -62,7 +64,6 @@ namespace demonware std::lock_guard _(this->mutex_); this->incoming_queue_.push(std::string(buf, len)); - //this->parsePacket(Utils::String(buf, len)); return len; } @@ -100,7 +101,7 @@ namespace demonware } } - void service_server::call_handler(uint8_t type, const std::string& data) + void service_server::call_handler(const uint8_t type, const std::string& data) { if (this->services_.find(type) != this->services_.end()) { @@ -117,7 +118,7 @@ namespace demonware if (!this->incoming_queue_.empty()) { std::lock_guard _(this->mutex_); - const std::string packet = this->incoming_queue_.front(); + const auto packet = this->incoming_queue_.front(); this->incoming_queue_.pop(); this->parse_packet(packet); @@ -131,7 +132,7 @@ namespace demonware try { - while (!packet.empty()) + while (buffer.has_more_data()) { int size; buffer.read_int32(&size); @@ -170,7 +171,10 @@ namespace demonware p_buffer.read_int32(&iv); auto iv_hash = utils::cryptography::tiger::compute(std::string(reinterpret_cast(&iv), 4)); - //pBuffer.get_buffer() = utils::cryptography::des3::decrypt(pBuffer.get_buffer(), iv_hash, Components::DemonWare::GetKey(false)); + + const std::string key(reinterpret_cast(dw::get_key(false)), 24); + p_buffer = byte_buffer{ utils::cryptography::des3::decrypt(p_buffer.get_remaining(), iv_hash, key) }; + p_buffer.set_use_data_types(false); int checksum; p_buffer.read_int32(&checksum); @@ -181,7 +185,7 @@ namespace demonware printf("DW: Handling message of type %d (encrypted: %d)\n", type, enc); this->reply_sent_ = false; - this->call_handler(type, p_buffer.get_buffer()); + this->call_handler(type, p_buffer.get_remaining()); if (!this->reply_sent_ && type != 7) { diff --git a/src/game/demonware/services/bdDML.cpp b/src/game/demonware/services/bdDML.cpp new file mode 100644 index 0000000..8bc7fc0 --- /dev/null +++ b/src/game/demonware/services/bdDML.cpp @@ -0,0 +1,29 @@ +#include +#include "bdDML.hpp" +#include "../data_types.hpp" + +namespace demonware +{ + bdDML::bdDML() + { + this->register_service(2, &bdDML::get_user_raw_data); + } + + void bdDML::get_user_raw_data(i_server* server, byte_buffer* /*buffer*/) const + { + auto result = new bdDMLRawData; + result->country_code = "US"; + result->country_code = "'Murica"; + result->region = "New York"; + result->city = "New York"; + result->latitude = 0; + result->longitude = 0; + + result->asn = 0x2119; + result->timezone = "+01:00"; + + auto reply = server->create_reply(this->get_sub_type()); + reply->add(result); + reply->send(); + } +} diff --git a/src/game/demonware/services/bdDML.hpp b/src/game/demonware/services/bdDML.hpp new file mode 100644 index 0000000..d6cd693 --- /dev/null +++ b/src/game/demonware/services/bdDML.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "../i_service.hpp" + +namespace demonware +{ + class bdDML final : public i_generic_service<27> + { + public: + bdDML(); + + private: + void get_user_raw_data(i_server* server, byte_buffer* buffer) const; + }; +} diff --git a/src/game/demonware/services/bdLSGHello.cpp b/src/game/demonware/services/bdLSGHello.cpp new file mode 100644 index 0000000..83361bd --- /dev/null +++ b/src/game/demonware/services/bdLSGHello.cpp @@ -0,0 +1,26 @@ +#include +#include "bdLSGHello.hpp" +#include "module/dw.hpp" + +namespace demonware +{ + void bdLSGHello::call_service(i_server* server, const std::string& data) + { + bit_buffer buffer(data); + + bool more_data; + buffer.set_use_data_types(false); + buffer.read_bool(&more_data); + buffer.set_use_data_types(true); + + uint32_t seed, title_id; + buffer.read_uint32(&title_id); + buffer.read_uint32(&seed); + + uint8_t ticket[128]; + buffer.read_bytes(sizeof(ticket), ticket); + + dw::set_key(true, ticket); + dw::set_key(false, ticket); + } +} diff --git a/src/game/demonware/services/bdLSGHello.hpp b/src/game/demonware/services/bdLSGHello.hpp new file mode 100644 index 0000000..ae7e952 --- /dev/null +++ b/src/game/demonware/services/bdLSGHello.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "../i_service.hpp" + +namespace demonware +{ + class bdLSGHello final : public i_generic_service<7> + { + public: + void call_service(i_server* server, const std::string& data) override; + }; +} diff --git a/src/game/demonware/services/bdSteamAuth.cpp b/src/game/demonware/services/bdSteamAuth.cpp new file mode 100644 index 0000000..58fa953 --- /dev/null +++ b/src/game/demonware/services/bdSteamAuth.cpp @@ -0,0 +1,60 @@ +#include +#include "bdSteamAuth.hpp" +#include "game/structs.hpp" +#include "steam/steam.hpp" +#include "utils/cryptography.hpp" + +namespace demonware +{ + void bdSteamAuth::call_service(i_server* server, const std::string& data) + { + bit_buffer buffer(data); + + bool more_data; + buffer.set_use_data_types(false); + buffer.read_bool(&more_data); + buffer.set_use_data_types(true); + + uint32_t seed, title_id, ticket_size; + buffer.read_uint32(&seed); + buffer.read_uint32(&title_id); + buffer.read_uint32(&ticket_size); + + uint8_t ticket[1024]; + buffer.read_bytes(std::min(ticket_size, static_cast(sizeof(ticket))), ticket); + + game::native::bdAuthTicket auth_ticket{}; + std::memset(&auth_ticket, 0xA, sizeof auth_ticket); + + auth_ticket.m_magicNumber = 0x0EFBDADDE; + auth_ticket.m_type = 0; + auth_ticket.m_titleID = title_id; + auth_ticket.m_userID = steam::SteamUser()->GetSteamID().bits; + + auto key = utils::cryptography::tiger::compute("Open-IW5"); + + strcpy_s(auth_ticket.m_username, "Open-IW5 User"); + std::memcpy(auth_ticket.m_sessionKey, key.data(), 24); + auth_ticket.m_timeIssued = static_cast(time(nullptr)); + + uint8_t lsg_ticket[128]; + ZeroMemory(&lsg_ticket, sizeof lsg_ticket); + std::memcpy(lsg_ticket, key.data(), 24); + + const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast(&seed), 4)); + + const std::string enc_key (reinterpret_cast(&ticket[32]), 24); + auto enc_ticket = utils::cryptography::des3::encrypt(std::string(reinterpret_cast(&auth_ticket), sizeof(auth_ticket)), iv,enc_key ); + + bit_buffer response; + response.set_use_data_types(false); + response.write_bool(false); + response.write_uint32(700); + response.write_uint32(seed); + response.write_bytes(enc_ticket.size(), enc_ticket.data()); + response.write_bytes(sizeof(lsg_ticket), lsg_ticket); + + auto reply = server->create_message(29); + reply->send(&response, false); + } +} diff --git a/src/game/demonware/services/bdSteamAuth.hpp b/src/game/demonware/services/bdSteamAuth.hpp new file mode 100644 index 0000000..f977294 --- /dev/null +++ b/src/game/demonware/services/bdSteamAuth.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "../i_service.hpp" + +namespace demonware +{ + class bdSteamAuth final : public i_generic_service<28> + { + public: + void call_service(i_server* server, const std::string& data) override; + }; +} diff --git a/src/game/demonware/services/bdStorage.cpp b/src/game/demonware/services/bdStorage.cpp new file mode 100644 index 0000000..1e273bb --- /dev/null +++ b/src/game/demonware/services/bdStorage.cpp @@ -0,0 +1,266 @@ +#include +#include "bdStorage.hpp" +#include "../data_types.hpp" +#include "utils/cryptography.hpp" +#include "utils/nt.hpp" +#include "utils/io.hpp" + +namespace demonware +{ + bdStorage::bdStorage() + { + this->register_service(1, &bdStorage::set_legacy_user_file); + this->register_service(3, &bdStorage::get_legacy_user_file); + this->register_service(5, &bdStorage::list_legacy_user_files); + this->register_service(6, &bdStorage::list_publisher_files); + this->register_service(7, &bdStorage::get_publisher_file); + this->register_service(10, &bdStorage::set_user_file); + this->register_service(11, &bdStorage::delete_user_file); + this->register_service(12, &bdStorage::get_user_file); + + this->map_publisher_resource("heatmap\\.raw", DW_HEATMAP); + this->map_publisher_resource("motd-.*\\.txt", DW_MOTD); + this->map_publisher_resource("online_mp\\.img", DW_IMG); + this->map_publisher_resource("online_[Tt][Uu][0-9]+_mp_.+\\.wad", DW_WAD); + this->map_publisher_resource("playlists(_.+)?\\.aggr", DW_PLAYLIST); + this->map_publisher_resource("social_[Tt][Uu][0-9]+\\.cfg", DW_CONFIG); + this->map_publisher_resource("iotd-.*\\.txt", DW_IOTD_TXT); + this->map_publisher_resource("iotd-.*\\.jpg", DW_IOTD_IMG); + } + + void bdStorage::map_publisher_resource(const std::string& expression, const INT id) + { + const auto res = FindResource(::utils::nt::module(), MAKEINTRESOURCE(id), RT_RCDATA); + if (!res) return; + + const auto handle = LoadResource(nullptr, res); + if (!handle) return; + + std::string data(LPSTR(LockResource(handle)), SizeofResource(nullptr, res)); + + publisher_resources_.emplace_back(std::regex{expression}, data); + } + + bool bdStorage::load_publisher_resource(const std::string& name, std::string& buffer) + { + for(const auto& resource : this->publisher_resources_) + { + if (std::regex_match(name, resource.first)) + { + buffer = resource.second; + return true; + } + } + + printf("DW: Missing publisher file: %s\n", name.data()); + + return false; + } + + std::string bdStorage::get_user_file_path(const std::string& name) + { + return "players2/user/" + name; + } + + void bdStorage::set_legacy_user_file(i_server* server, byte_buffer* buffer) const + { + bool priv; + std::string filename, data; + + buffer->read_string(&filename); + buffer->read_bool(&priv); + buffer->read_blob(&data); + + printf("DW: Storing user file: %s\n", filename.data()); + + const auto path = get_user_file_path(filename); + utils::io::write_file(path, data); + + auto info = new bdFileInfo; + + info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data()); + info->filename = filename; + info->create_time = uint32_t(time(nullptr)); + info->modified_time = info->create_time; + info->file_size = uint32_t(data.size()); + info->owner_id = 0; + info->priv = priv; + + auto reply = server->create_reply(this->get_sub_type()); + reply->add(info); + reply->send(); + } + + void bdStorage::get_legacy_user_file(i_server* server, byte_buffer* buffer) const + { + std::string filename, data; + buffer->read_string(&filename); + + printf("DW: Loading user file: %s\n", filename.data()); + + const auto path = get_user_file_path(filename); + if(utils::io::read_file(path, &data)) + { + auto reply = server->create_reply(this->get_sub_type()); + reply->add(new bdFileData(data)); + reply->send(); + } + else + { + server->create_reply(this->get_sub_type(), game::native::BD_NO_FILE)->send(); + } + } + + void bdStorage::list_legacy_user_files(i_server* server, byte_buffer* buffer) const + { + uint64_t unk; + uint32_t date; + uint16_t num_results, offset; + std::string filename, data; + + buffer->read_uint64(&unk); + buffer->read_uint32(&date); + buffer->read_uint16(&num_results); + buffer->read_uint16(&offset); + buffer->read_string(&filename); + + auto reply = server->create_reply(this->get_sub_type()); + + const auto path = get_user_file_path(filename); + if(utils::io::read_file(path, &data)) + { + auto* info = new bdFileInfo; + + info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data()); + info->filename = filename; + info->create_time = 0; + info->modified_time = info->create_time; + info->file_size = uint32_t(data.size()); + info->owner_id = 0; + info->priv = false; + + reply->add(info); + } + + reply->send(); + } + + void bdStorage::list_publisher_files(i_server* server, byte_buffer* buffer) + { + uint32_t date; + uint16_t num_results, offset; + std::string filename, data; + + buffer->read_uint32(&date); + buffer->read_uint16(&num_results); + buffer->read_uint16(&offset); + buffer->read_string(&filename); + + auto reply = server->create_reply(this->get_sub_type()); + + if (this->load_publisher_resource(filename, data)) + { + auto* info = new bdFileInfo; + + info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data()); + info->filename = filename; + info->create_time = 0; + info->modified_time = info->create_time; + info->file_size = uint32_t(data.size()); + info->owner_id = 0; + info->priv = false; + + reply->add(info); + } + + reply->send(); + } + + void bdStorage::get_publisher_file(i_server* server, byte_buffer* buffer) + { + std::string filename; + buffer->read_string(&filename); + + printf("DW: Loading publisher file: %s\n", filename.data()); + + std::string data; + if (this->load_publisher_resource(filename, data)) + { + auto reply = server->create_reply(this->get_sub_type()); + reply->add(new bdFileData(data)); + reply->send(); + } + else + { + server->create_reply(this->get_sub_type(), game::native::BD_NO_FILE)->send(); + } + } + + void bdStorage::delete_user_file(i_server* server, byte_buffer* buffer) const + { + uint64_t owner; + std::string game, filename; + + buffer->read_string(&game); + buffer->read_string(&filename); + buffer->read_uint64(&owner); + + // Really remove the file? + + auto reply = server->create_reply(this->get_sub_type()); + reply->send(); + } + + void bdStorage::set_user_file(i_server* server, byte_buffer* buffer) const + { + bool priv; + uint64_t owner; + std::string game, filename, data; + + buffer->read_string(&game); + buffer->read_string(&filename); + buffer->read_bool(&priv); + buffer->read_blob(&data); + buffer->read_uint64(&owner); + + const auto path = get_user_file_path(filename); + utils::io::write_file(path, data); + + auto info = new bdFileInfo; + + info->file_id = *reinterpret_cast(utils::cryptography::sha1::compute(filename).data()); + info->filename = filename; + info->create_time = uint32_t(time(nullptr)); + info->modified_time = info->create_time; + info->file_size = uint32_t(data.size()); + info->owner_id = owner; + info->priv = priv; + + auto reply = server->create_reply(this->get_sub_type()); + reply->add(info); + reply->send(); + } + + void bdStorage::get_user_file(i_server* server, byte_buffer* buffer) const + { + uint64_t owner; + std::string game, filename, platform, data; + + buffer->read_string(&game); + buffer->read_string(&filename); + buffer->read_uint64(&owner); + buffer->read_string(&platform); + + const auto path = get_user_file_path(filename); + if(utils::io::read_file(path, &data)) + { + auto reply = server->create_reply(this->get_sub_type()); + reply->add(new bdFileData(data)); + reply->send(); + } + else + { + server->create_reply(this->get_sub_type(), game::native::BD_NO_FILE)->send(); + } + } +} diff --git a/src/game/demonware/services/bdStorage.hpp b/src/game/demonware/services/bdStorage.hpp new file mode 100644 index 0000000..811ff9f --- /dev/null +++ b/src/game/demonware/services/bdStorage.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "../i_service.hpp" + +namespace demonware +{ + class bdStorage final : public i_generic_service<10> + { + public: + bdStorage(); + + private: + std::vector> publisher_resources_; + + void set_legacy_user_file(i_server* server, byte_buffer* buffer) const; + void get_legacy_user_file(i_server* server, byte_buffer* buffer) const; + void list_legacy_user_files(i_server* server, byte_buffer* buffer) const; + void list_publisher_files(i_server* server, byte_buffer* buffer); + void get_publisher_file(i_server* server, byte_buffer* buffer); + void delete_user_file(i_server* server, byte_buffer* buffer) const; + void set_user_file(i_server* server, byte_buffer* buffer) const; + void get_user_file(i_server* server, byte_buffer* buffer) const; + + void map_publisher_resource(const std::string& expression, INT id); + bool load_publisher_resource(const std::string& name, std::string& buffer); + + static std::string get_user_file_path(const std::string& name); + }; +} diff --git a/src/game/demonware/services/bdTitleUtilities.cpp b/src/game/demonware/services/bdTitleUtilities.cpp new file mode 100644 index 0000000..fb295b7 --- /dev/null +++ b/src/game/demonware/services/bdTitleUtilities.cpp @@ -0,0 +1,21 @@ +#include +#include "bdTitleUtilities.hpp" +#include "../data_types.hpp" + +namespace demonware +{ + bdTitleUtilities::bdTitleUtilities() + { + this->register_service(6, &bdTitleUtilities::get_server_time); + } + + void bdTitleUtilities::get_server_time(i_server* server, byte_buffer* /*buffer*/) const + { + const auto time_result = new bdTimeStamp; + time_result->unix_time = uint32_t(time(nullptr)); + + auto reply = server->create_reply(this->get_sub_type()); + reply->add(time_result); + reply->send(); + } +} diff --git a/src/game/demonware/services/bdTitleUtilities.hpp b/src/game/demonware/services/bdTitleUtilities.hpp new file mode 100644 index 0000000..72d292e --- /dev/null +++ b/src/game/demonware/services/bdTitleUtilities.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "../i_service.hpp" + +namespace demonware +{ + class bdTitleUtilities final : public i_generic_service<12> + { + public: + bdTitleUtilities(); + + private: + void get_server_time(i_server* server, byte_buffer* buffer) const; + }; +} diff --git a/src/game/demonware/stun_server.cpp b/src/game/demonware/stun_server.cpp index dc5d2a7..64869d7 100644 --- a/src/game/demonware/stun_server.cpp +++ b/src/game/demonware/stun_server.cpp @@ -1,6 +1,6 @@ #include #include -#include "stun_server.hpp" +#include "module/dw.hpp" #include "utils/cryptography.hpp" #include "byte_buffer.hpp" @@ -28,7 +28,7 @@ namespace demonware buffer.write_uint32(ip); // external ip buffer.write_uint16(3074); // port - //Components::DemonWare::SendDatagramPacket(s, buffer, to, tolen); + dw::send_datagram_packet(s, buffer.get_buffer(), to, tolen); } void stun_server::nat_discovery(SOCKET s, const sockaddr* to, int tolen) const @@ -45,7 +45,7 @@ namespace demonware buffer.write_uint32(this->get_address()); // server ip buffer.write_uint16(3074); // server port - //Components::DemonWare::SendDatagramPacket(s, buffer, to, tolen); + dw::send_datagram_packet(s, buffer.get_buffer(), to, tolen); } int stun_server::send(const SOCKET s, const char* buf, int len, const sockaddr* to, int tolen) const diff --git a/src/game/structs.hpp b/src/game/structs.hpp index c9be37f..94d2774 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -4,6 +4,346 @@ namespace game { namespace native { + enum bdLobbyErrorCode : uint32_t + { + BD_NO_ERROR = 0x0, + BD_TOO_MANY_TASKS = 0x1, + BD_NOT_CONNECTED = 0x2, + BD_SEND_FAILED = 0x3, + BD_HANDLE_TASK_FAILED = 0x4, + BD_START_TASK_FAILED = 0x5, + BD_RESULT_EXCEEDS_BUFFER_SIZE = 0x64, + BD_ACCESS_DENIED = 0x65, + BD_EXCEPTION_IN_DB = 0x66, + BD_MALFORMED_TASK_HEADER = 0x67, + BD_INVALID_ROW = 0x68, + BD_EMPTY_ARG_LIST = 0x69, + BD_PARAM_PARSE_ERROR = 0x6A, + BD_PARAM_MISMATCHED_TYPE = 0x6B, + BD_SERVICE_NOT_AVAILABLE = 0x6C, + BD_CONNECTION_RESET = 0x6D, + BD_INVALID_USER_ID = 0x6E, + BD_LOBBY_PROTOCOL_VERSION_FAILURE = 0x6F, + BD_LOBBY_INTERNAL_FAILURE = 0x70, + BD_LOBBY_PROTOCOL_ERROR = 0x71, + BD_LOBBY_FAILED_TO_DECODE_UTF8 = 0x72, + BD_LOBBY_ASCII_EXPECTED = 0x73, + BD_ASYNCHRONOUS_ERROR = 0xC8, + BD_STREAMING_COMPLETE = 0xC9, + BD_MEMBER_NO_PROPOSAL = 0x12C, + BD_TEAMNAME_ALREADY_EXISTS = 0x12D, + BD_MAX_TEAM_MEMBERSHIPS_LIMITED = 0x12E, + BD_MAX_TEAM_OWNERSHIPS_LIMITED = 0x12F, + BD_NOT_A_TEAM_MEMBER = 0x130, + BD_INVALID_TEAM_ID = 0x131, + BD_INVALID_TEAM_NAME = 0x132, + BD_NOT_A_TEAM_OWNER = 0x133, + BD_NOT_AN_ADMIN_OR_OWNER = 0x134, + BD_MEMBER_PROPOSAL_EXISTS = 0x135, + BD_MEMBER_EXISTS = 0x136, + BD_TEAM_FULL = 0x137, + BD_VULGAR_TEAM_NAME = 0x138, + BD_TEAM_USERID_BANNED = 0x139, + BD_TEAM_EMPTY = 0x13A, + BD_INVALID_TEAM_PROFILE_QUERY_ID = 0x13B, + BD_TEAMNAME_TOO_SHORT = 0x13C, + BD_UNIQUE_PROFILE_DATA_EXISTS_ALREADY = 0x13D, + BD_INVALID_LEADERBOARD_ID = 0x190, + BD_INVALID_STATS_SET = 0x191, + BD_EMPTY_STATS_SET_IGNORED = 0x193, + BD_NO_DIRECT_ACCESS_TO_ARBITRATED_LBS = 0x194, + BD_STATS_WRITE_PERMISSION_DENIED = 0x195, + BD_STATS_WRITE_TYPE_DATA_TYPE_MISMATCH = 0x196, + BD_NO_STATS_FOR_USER = 0x197, + BD_INVALID_ACCESS_TO_UNRANKED_LB = 0x198, + BD_INVALID_EXTERNAL_TITLE_ID = 0x199, + BD_DIFFERENT_LEADERBOARD_SCHEMAS = 0x19A, + BD_TOO_MANY_LEADERBOARDS_REQUESTED = 0x19B, + BD_ENTITLEMENTS_ERROR = 0x19C, + BD_ENTITLEMENTS_INVALID_TITLEID = 0x19D, + BD_ENTITLEMENTS_INVALID_LEADERBOARDID = 0x19E, + BD_ENTITLEMENTS_INVALID_GET_MODE_FOR_TITLE = 0x19F, + BD_ENTITLEMENTS_URL_CONNECTION_ERROR = 0x1A0, + BD_ENTITLEMENTS_CONFIG_ERROR = 0x1A1, + BD_ENTITLEMENTS_NAMED_PARENT_ERROR = 0x1A2, + BD_ENTITLEMENTS_NAMED_KEY_ERROR = 0x1A3, + BD_TOO_MANY_ENTITY_IDS_REQUESTED = 0x1A4, + BD_STATS_READ_FAILED = 0x1A5, + BD_INVALID_TITLE_ID = 0x1F4, + BD_MESSAGING_INVALID_MAIL_ID = 0x258, + BD_SELF_BLOCK_NOT_ALLOWED = 0x259, + BD_GLOBAL_MESSAGE_ACCESS_DENIED = 0x25A, + BD_GLOBAL_MESSAGES_USER_LIMIT_EXCEEDED = 0x25B, + BD_MESSAGING_SENDER_DOES_NOT_EXIST = 0x25C, + BD_AUTH_NO_ERROR = 0x2BC, + BD_AUTH_BAD_REQUEST = 0x2BD, + BD_AUTH_SERVER_CONFIG_ERROR = 0x2BE, + BD_AUTH_BAD_TITLE_ID = 0x2BF, + BD_AUTH_BAD_ACCOUNT = 0x2C0, + BD_AUTH_ILLEGAL_OPERATION = 0x2C1, + BD_AUTH_INCORRECT_LICENSE_CODE = 0x2C2, + BD_AUTH_CREATE_USERNAME_EXISTS = 0x2C3, + BD_AUTH_CREATE_USERNAME_ILLEGAL = 0x2C4, + BD_AUTH_CREATE_USERNAME_VULGAR = 0x2C5, + BD_AUTH_CREATE_MAX_ACC_EXCEEDED = 0x2C6, + BD_AUTH_MIGRATE_NOT_SUPPORTED = 0x2C7, + BD_AUTH_TITLE_DISABLED = 0x2C8, + BD_AUTH_ACCOUNT_EXPIRED = 0x2C9, + BD_AUTH_ACCOUNT_LOCKED = 0x2CA, + BD_AUTH_UNKNOWN_ERROR = 0x2CB, + BD_AUTH_INCORRECT_PASSWORD = 0x2CC, + BD_AUTH_IP_NOT_IN_ALLOWED_RANGE = 0x2CD, + BD_AUTH_WII_TOKEN_VERIFICATION_FAILED = 0x2CE, + BD_AUTH_WII_AUTHENTICATION_FAILED = 0x2CF, + BD_AUTH_IP_KEY_LIMIT_REACHED = 0x2D0, + BD_AUTH_INVALID_GSPID = 0x2D1, + BD_AUTH_INVALID_IP_RANGE_ID = 0x2D2, + BD_AUTH_3DS_TOKEN_VERIFICATION_FAILED = 0x2D1, + BD_AUTH_3DS_AUTHENTICATION_FAILED = 0x2D2, + BD_AUTH_STEAM_APP_ID_MISMATCH = 0x2D3, + BD_AUTH_ABACCOUNTS_APP_ID_MISMATCH = 0x2D4, + BD_AUTH_CODO_USERNAME_NOT_SET = 0x2D5, + BD_AUTH_WIIU_TOKEN_VERIFICATION_FAILED = 0x2D6, + BD_AUTH_WIIU_AUTHENTICATION_FAILED = 0x2D7, + BD_AUTH_CODO_USERNAME_NOT_BASE64 = 0x2D8, + BD_AUTH_CODO_USERNAME_NOT_UTF8 = 0x2D9, + BD_AUTH_TENCENT_TICKET_EXPIRED = 0x2DA, + BD_AUTH_PS3_SERVICE_ID_MISMATCH = 0x2DB, + BD_AUTH_CODOID_NOT_WHITELISTED = 0x2DC, + BD_AUTH_PLATFORM_TOKEN_ERROR = 0x2DD, + BD_AUTH_JSON_FORMAT_ERROR = 0x2DE, + BD_AUTH_REPLY_CONTENT_ERROR = 0x2DF, + BD_AUTH_THIRD_PARTY_TOKEN_EXPIRED = 0x2E0, + BD_AUTH_CONTINUING = 0x2E1, + BD_AUTH_PLATFORM_DEVICE_ID_ERROR = 0x2E4, + BD_NO_PROFILE_INFO_EXISTS = 0x320, + BD_FRIENDSHIP_NOT_REQUSTED = 0x384, + BD_NOT_A_FRIEND = 0x385, + BD_SELF_FRIENDSHIP_NOT_ALLOWED = 0x387, + BD_FRIENDSHIP_EXISTS = 0x388, + BD_PENDING_FRIENDSHIP_EXISTS = 0x389, + BD_USERID_BANNED = 0x38A, + BD_FRIENDS_FULL = 0x38C, + BD_FRIENDS_NO_RICH_PRESENCE = 0x38D, + BD_RICH_PRESENCE_TOO_LARGE = 0x38E, + BD_NO_FILE = 0x3E8, + BD_PERMISSION_DENIED = 0x3E9, + BD_FILESIZE_LIMIT_EXCEEDED = 0x3EA, + BD_FILENAME_MAX_LENGTH_EXCEEDED = 0x3EB, + BD_EXTERNAL_STORAGE_SERVICE_ERROR = 0x3EC, + BD_CHANNEL_DOES_NOT_EXIST = 0x44D, + BD_CHANNEL_ALREADY_SUBSCRIBED = 0x44E, + BD_CHANNEL_NOT_SUBSCRIBED = 0x44F, + BD_CHANNEL_FULL = 0x450, + BD_CHANNEL_SUBSCRIPTIONS_FULL = 0x451, + BD_CHANNEL_NO_SELF_WHISPERING = 0x452, + BD_CHANNEL_ADMIN_REQUIRED = 0x453, + BD_CHANNEL_TARGET_NOT_SUBSCRIBED = 0x454, + BD_CHANNEL_REQUIRES_PASSWORD = 0x455, + BD_CHANNEL_TARGET_IS_SELF = 0x456, + BD_CHANNEL_PUBLIC_BAN_NOT_ALLOWED = 0x457, + BD_CHANNEL_USER_BANNED = 0x458, + BD_CHANNEL_PUBLIC_PASSWORD_NOT_ALLOWED = 0x459, + BD_CHANNEL_PUBLIC_KICK_NOT_ALLOWED = 0x45A, + BD_CHANNEL_MUTED = 0x45B, + BD_EVENT_DESC_TRUNCATED = 0x4B0, + BD_CONTENT_UNLOCK_UNKNOWN_ERROR = 0x514, + BD_UNLOCK_KEY_INVALID = 0x515, + BD_UNLOCK_KEY_ALREADY_USED_UP = 0x516, + BD_SHARED_UNLOCK_LIMIT_REACHED = 0x517, + BD_DIFFERENT_HARDWARE_ID = 0x518, + BD_INVALID_CONTENT_OWNER = 0x519, + BD_CONTENT_UNLOCK_INVALID_USER = 0x51A, + BD_CONTENT_UNLOCK_INVALID_CATEGORY = 0x51B, + BD_KEY_ARCHIVE_INVALID_WRITE_TYPE = 0x5DC, + BD_KEY_ARCHIVE_EXCEEDED_MAX_IDS_PER_REQUEST = 0x5DD, + BD_BANDWIDTH_TEST_TRY_AGAIN = 0x712, + BD_BANDWIDTH_TEST_STILL_IN_PROGRESS = 0x713, + BD_BANDWIDTH_TEST_NOT_PROGRESS = 0x714, + BD_BANDWIDTH_TEST_SOCKET_ERROR = 0x715, + BD_INVALID_SESSION_NONCE = 0x76D, + BD_ARBITRATION_FAILURE = 0x76F, + BD_ARBITRATION_USER_NOT_REGISTERED = 0x771, + BD_ARBITRATION_NOT_CONFIGURED = 0x772, + BD_CONTENTSTREAMING_FILE_NOT_AVAILABLE = 0x7D0, + BD_CONTENTSTREAMING_STORAGE_SPACE_EXCEEDED = 0x7D1, + BD_CONTENTSTREAMING_NUM_FILES_EXCEEDED = 0x7D2, + BD_CONTENTSTREAMING_UPLOAD_BANDWIDTH_EXCEEDED = 0x7D3, + BD_CONTENTSTREAMING_FILENAME_MAX_LENGTH_EXCEEDED = 0x7D4, + BD_CONTENTSTREAMING_MAX_THUMB_DATA_SIZE_EXCEEDED = 0x7D5, + BD_CONTENTSTREAMING_DOWNLOAD_BANDWIDTH_EXCEEDED = 0x7D6, + BD_CONTENTSTREAMING_NOT_ENOUGH_DOWNLOAD_BUFFER_SPACE = 0x7D7, + BD_CONTENTSTREAMING_SERVER_NOT_CONFIGURED = 0x7D8, + BD_CONTENTSTREAMING_INVALID_APPLE_RECEIPT = 0x7DA, + BD_CONTENTSTREAMING_APPLE_STORE_NOT_AVAILABLE = 0x7DB, + BD_CONTENTSTREAMING_APPLE_RECEIPT_FILENAME_MISMATCH = 0x7DC, + BD_CONTENTSTREAMING_HTTP_ERROR = 0x7E4, + BD_CONTENTSTREAMING_FAILED_TO_START_HTTP = 0x7E5, + BD_CONTENTSTREAMING_LOCALE_INVALID = 0x7E6, + BD_CONTENTSTREAMING_LOCALE_MISSING = 0x7E7, + BD_VOTERANK_ERROR_EMPTY_RATING_SUBMISSION = 0x7EE, + BD_VOTERANK_ERROR_MAX_VOTES_EXCEEDED = 0x7EF, + BD_VOTERANK_ERROR_INVALID_RATING = 0x7F0, + BD_MAX_NUM_TAGS_EXCEEDED = 0x82A, + BD_TAGGED_COLLECTION_DOES_NOT_EXIST = 0x82B, + BD_EMPTY_TAG_ARRAY = 0x82C, + BD_INVALID_QUERY_ID = 0x834, + BD_NO_ENTRY_TO_UPDATE = 0x835, + BD_SESSION_INVITE_EXISTS = 0x836, + BD_INVALID_SESSION_ID = 0x837, + BD_ATTACHMENT_TOO_LARGE = 0x838, + BD_INVALID_GROUP_ID = 0xAF0, + BD_MAIL_INVALID_MAIL_ID_ERROR = 0xB55, + BD_UCD_SERVICE_ERROR = 0xC80, + BD_UCD_SERVICE_DISABLED = 0xC81, + BD_UCD_UNINTIALIZED_ERROR = 0xC82, + BD_UCD_ACCOUNT_ALREADY_REGISTERED = 0xC83, + BD_UCD_ACCOUNT_NOT_REGISTERED = 0xC84, + BD_UCD_AUTH_ATTEMPT_FAILED = 0xC85, + BD_UCD_ACCOUNT_LINKING_ERROR = 0xC86, + BD_UCD_ENCRYPTION_ERROR = 0xC87, + BD_UCD_ACCOUNT_DATA_INVALID = 0xC88, + BD_UCD_ACCOUNT_DATA_INVALID_FIRSTNAME = 0xC89, + BD_UCD_ACCOUNT_DATA_INVALID_LASTNAME = 0xC8A, + BD_UCD_ACCOUNT_DATA_INVALID_DOB = 0xC8B, + BD_UCD_ACCOUNT_DATA_INVALID_EMAIL = 0xC8C, + BD_UCD_ACCOUNT_DATA_INVALID_COUNTRY = 0xC8D, + BD_UCD_ACCOUNT_DATA_INVALID_POSTCODE = 0xC8E, + BD_UCD_ACCOUNT_DATA_INVALID_PASSWORD = 0xC8F, + BD_UCD_ACCOUNT_NAME_ALREADY_RESISTERED = 0xC94, + BD_UCD_ACCOUNT_EMAIL_ALREADY_RESISTERED = 0xC95, + BD_UCD_GUEST_ACCOUNT_AUTH_CONFLICT = 0xC96, + BD_TWITCH_SERVICE_ERROR = 0xC1D, + BD_TWITCH_ACCOUNT_ALREADY_LINKED = 0xC1E, + BD_TWITCH_NO_LINKED_ACCOUNT = 0xC1F, + BD_YOUTUBE_SERVICE_ERROR = 0xCE5, + BD_YOUTUBE_SERVICE_COMMUNICATION_ERROR = 0xCE6, + BD_YOUTUBE_USER_DENIED_AUTHORIZATION = 0xCE7, + BD_YOUTUBE_AUTH_MAX_TIME_EXCEEDED = 0xCE8, + BD_YOUTUBE_USER_UNAUTHORIZED = 0xCE9, + BD_YOUTUBE_UPLOAD_MAX_TIME_EXCEEDED = 0xCEA, + BD_YOUTUBE_DUPLICATE_UPLOAD = 0xCEB, + BD_YOUTUBE_FAILED_UPLOAD = 0xCEC, + BD_YOUTUBE_ACCOUNT_ALREADY_REGISTERED = 0xCED, + BD_YOUTUBE_ACCOUNT_NOT_REGISTERED = 0xCEE, + BD_YOUTUBE_CONTENT_SERVER_ERROR = 0xCEF, + BD_YOUTUBE_UPLOAD_DOES_NOT_EXIST = 0xCF0, + BD_YOUTUBE_NO_LINKED_ACCOUNT = 0xCF1, + BD_YOUTUBE_DEVELOPER_TAGS_INVALID = 0xCF2, + BD_TWITTER_AUTH_ATTEMPT_FAILED = 0xDAD, + BD_TWITTER_AUTH_TOKEN_INVALID = 0xDAE, + BD_TWITTER_UPDATE_LIMIT_REACHED = 0xDAF, + BD_TWITTER_UNAVAILABLE = 0xDB0, + BD_TWITTER_ERROR = 0xDB1, + BD_TWITTER_TIMED_OUT = 0xDB2, + BD_TWITTER_DISABLED_FOR_USER = 0xDB3, + BD_TWITTER_ACCOUNT_AMBIGUOUS = 0xDB4, + BD_TWITTER_MAXIMUM_ACCOUNTS_REACHED = 0xDB5, + BD_TWITTER_ACCOUNT_NOT_REGISTERED = 0xDB6, + BD_TWITTER_DUPLICATE_STATUS = 0xDB7, + BD_TWITTER_ACCOUNT_ALREADY_REGISTERED = 0xE1C, + BD_FACEBOOK_AUTH_ATTEMPT_FAILED = 0xE11, + BD_FACEBOOK_AUTH_TOKEN_INVALID = 0xE12, + BD_FACEBOOK_PHOTO_DOES_NOT_EXIST = 0xE13, + BD_FACEBOOK_PHOTO_INVALID = 0xE14, + BD_FACEBOOK_PHOTO_ALBUM_FULL = 0xE15, + BD_FACEBOOK_UNAVAILABLE = 0xE16, + BD_FACEBOOK_ERROR = 0xE17, + BD_FACEBOOK_TIMED_OUT = 0xE18, + BD_FACEBOOK_DISABLED_FOR_USER = 0xE19, + BD_FACEBOOK_ACCOUNT_AMBIGUOUS = 0xE1A, + BD_FACEBOOK_MAXIMUM_ACCOUNTS_REACHED = 0xE1B, + BD_FACEBOOK_INVALID_NUM_PICTURES_REQUESTED = 0xE1C, + BD_FACEBOOK_VIDEO_DOES_NOT_EXIST = 0xE1D, + BD_FACEBOOK_ACCOUNT_ALREADY_REGISTERED = 0xE1E, + BD_APNS_INVALID_PAYLOAD = 0xE74, + BD_APNS_INVALID_TOKEN_LENGTH_ERROR = 0xE76, + BD_MAX_CONSOLEID_LENGTH_EXCEEDED = 0xEE1, + BD_MAX_WHITELIST_LENGTH_EXCEEDED = 0xEE2, + BD_USERGROUP_NAME_ALREADY_EXISTS = 0x1770, + BD_INVALID_USERGROUP_ID = 0x1771, + BD_USER_ALREADY_IN_USERGROUP = 0x1772, + BD_USER_NOT_IN_USERGROUP = 0x1773, + BD_INVALID_USERGROUP_MEMBER_TYPE = 0x1774, + BD_TOO_MANY_MEMBERS_REQUESTED = 0x1775, + BD_USERGROUP_NAME_TOO_SHORT = 0x1776, + BD_RICH_PRESENCE_DATA_TOO_LARGE = 0x1A90, + BD_RICH_PRESENCE_TOO_MANY_USERS = 0x1A91, + BD_PRESENCE_DATA_TOO_LARGE = 0x283C, + BD_PRESENCE_TOO_MANY_USERS = 0x283D, + BD_USER_LOGGED_IN_OTHER_TITLE = 0x283E, + BD_USER_NOT_LOGGED_IN = 0x283F, + BD_SUBSCRIPTION_TOO_MANY_USERS = 0x1B58, + BD_SUBSCRIPTION_TICKET_PARSE_ERROR = 0x1B59, + BD_CODO_ID_INVALID_DATA = 0x1BBC, + BD_INVALID_MESSAGE_FORMAT = 0x1BBD, + BD_TLOG_TOO_MANY_MESSAGES = 0x1BBE, + BD_CODO_ID_NOT_IN_WHITELIST = 0x1BBF, + BD_TLOG_MESSAGE_TRANSFORMATION_ERROR = 0x1BC0, + BD_REWARDS_NOT_ENABLED = 0x1BC1, + BD_MARKETPLACE_ERROR = 0x1F40, + BD_MARKETPLACE_RESOURCE_NOT_FOUND = 0x1F41, + BD_MARKETPLACE_INVALID_CURRENCY = 0x1F42, + BD_MARKETPLACE_INVALID_PARAMETER = 0x1F43, + BD_MARKETPLACE_RESOURCE_CONFLICT = 0x1F44, + BD_MARKETPLACE_STORAGE_ERROR = 0x1F45, + BD_MARKETPLACE_INTEGRITY_ERROR = 0x1F46, + BD_MARKETPLACE_INSUFFICIENT_FUNDS_ERROR = 0x1F47, + BD_MARKETPLACE_MMP_SERVICE_ERROR = 0x1F48, + BD_MARKETPLACE_PRECONDITION_REQUIRED = 0x1F49, + BD_MARKETPLACE_ITEM_MULTIPLE_PURCHASE_ERROR = 0x1F4A, + BD_MARKETPLACE_MISSING_REQUIRED_ENTITLEMENT = 0x1F4B, + BD_MARKETPLACE_VALIDATION_ERROR = 0x1F4C, + BD_MARKETPLACE_TENCENT_PAYMENT_ERROR = 0x1F4D, + BD_MARKETPLACE_SKU_NOT_COUPON_ENABLED_ERROR = 0x1F4E, + BD_LEAGUE_INVALID_TEAM_SIZE = 0x1FA4, + BD_LEAGUE_INVALID_TEAM = 0x1FA5, + BD_LEAGUE_INVALID_SUBDIVISION = 0x1FA6, + BD_LEAGUE_INVALID_LEAGUE = 0x1FA7, + BD_LEAGUE_TOO_MANY_RESULTS_REQUESTED = 0x1FA8, + BD_LEAGUE_METADATA_TOO_LARGE = 0x1FA9, + BD_LEAGUE_TEAM_ICON_TOO_LARGE = 0x1FAA, + BD_LEAGUE_TEAM_NAME_TOO_LONG = 0x1FAB, + BD_LEAGUE_ARRAY_SIZE_MISMATCH = 0x1FAC, + BD_LEAGUE_SUBDIVISION_MISMATCH = 0x2008, + BD_LEAGUE_INVALID_WRITE_TYPE = 0x2009, + BD_LEAGUE_INVALID_STATS_DATA = 0x200A, + BD_LEAGUE_SUBDIVISION_UNRANKED = 0x200B, + BD_LEAGUE_CROSS_TEAM_STATS_WRITE_PREVENTED = 0x200C, + BD_LEAGUE_INVALID_STATS_SEASON = 0x200D, + BD_COMMERCE_ERROR = 0x206C, + BD_COMMERCE_RESOURCE_NOT_FOUND = 0x206D, + BD_COMMERCE_STORAGE_INVALID_PARAMETER = 0x206E, + BD_COMMERCE_APPLICATION_INVALID_PARAMETER = 0x206F, + BD_COMMERCE_RESOURCE_CONFLICT = 0x2070, + BD_COMMERCE_STORAGE_ERROR = 0x2071, + BD_COMMERCE_INTEGRITY_ERROR = 0x2072, + BD_COMMERCE_MMP_SERVICE_ERROR = 0x2073, + BD_COMMERCE_PERMISSION_DENIED = 0x2074, + BD_COMMERCE_INSUFFICIENT_FUNDS_ERROR = 0x2075, + BD_COMMERCE_UNKNOWN_CURRENCY = 0x2076, + BD_COMMERCE_INVALID_RECEIPT = 0x2077, + BD_COMMERCE_RECEIPT_USED = 0x2078, + BD_COMMERCE_TRANSACTION_ALREADY_APPLIED = 0x2079, + BD_COMMERCE_INVALID_CURRENCY_TYPE = 0x207A, + BD_CONNECTION_COUNTER_ERROR = 0x20D0, + BD_LINKED_ACCOUNTS_INVALID_CONTEXT = 0x2198, + BD_LINKED_ACCOUNTS_INVALID_PLATFORM = 0x2199, + BD_LINKED_ACCOUNTS_LINKED_ACCOUNTS_FETCH_ERROR = 0x219A, + BD_LINKED_ACCOUNTS_INVALID_ACCOUNT = 0x219B, + BD_GMSG_INVALID_CATEGORY_ID = 0x27D8, + BD_GMSG_CATEGORY_MEMBERSHIPS_LIMIT = 0x27D9, + BD_GMSG_NONMEMBER_POST_DISALLOWED = 0x27DA, + BD_GMSG_CATEGORY_DISALLOWS_CLIENT_TYPE = 0x27DB, + BD_GMSG_PAYLOAD_TOO_BIG = 0x27DC, + BD_GMSG_MEMBER_POST_DISALLOWED = 0x27DD, + BD_GMSG_OVERLOADED = 0x27DE, + BD_GMSG_USER_PERCATEGORY_POST_RATE_EXCEEDED = 0x27DF, + BD_GMSG_USER_GLOBAL_POST_RATE_EXCEEDED = 0x27E0, + BD_GMSG_GROUP_POST_RATE_EXCEEDED = 0x27E1, + BD_MAX_ERROR_CODE = 0x27E2, + }; + enum bdNATType : uint8_t { BD_NAT_UNKNOWN = 0x0, @@ -11,5 +351,22 @@ namespace game BD_NAT_MODERATE = 0x2, BD_NAT_STRICT = 0x3, }; + +#pragma pack(push, 1) + struct bdAuthTicket + { + unsigned int m_magicNumber; + char m_type; + unsigned int m_titleID; + unsigned int m_timeIssued; + unsigned int m_timeExpires; + unsigned __int64 m_licenseID; + unsigned __int64 m_userID; + char m_username[64]; + char m_sessionKey[24]; + char m_usingHashMagicNumber[3]; + char m_hash[4]; + }; +#pragma pack(pop) } } diff --git a/src/module/dw.cpp b/src/module/dw.cpp index 9412ce6..6e1b54d 100644 --- a/src/module/dw.cpp +++ b/src/module/dw.cpp @@ -1,24 +1,411 @@ #include -#include "loader/module_loader.hpp" +#include "dw.hpp" #include "utils/hook.hpp" #include "game/game.hpp" +#include "utils/nt.hpp" +#include "utils/cryptography.hpp" -class dw final : public module +#include "game/demonware/services/bdLSGHello.hpp" // 7 +#include "game/demonware/services/bdStorage.hpp" // 10 +#include "game/demonware/services/bdTitleUtilities.hpp" // 12 +#include "game/demonware/services/bdDML.hpp" // 27 +#include "game/demonware/services/bdSteamAuth.hpp" // 28 + +namespace demonware { -public: - dw() + namespace io { - // TODO Patch DW + int __stdcall send_to(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to, + const int tolen) + { + if (tolen == sizeof(sockaddr_in)) + { + const auto in_addr = reinterpret_cast(to); + const auto server = dw::find_stun_server_by_address(in_addr->sin_addr.s_addr); + if (server) return server->send(s, buf, len, to, tolen); + } + + return sendto(s, buf, len, flags, to, tolen); + } + + int __stdcall recv_from(const SOCKET s, char* buf, const int len, const int flags, sockaddr* from, int* fromlen) + { + auto res = dw::recv_datagam_packet(s, buf, len, from, fromlen); + if (res != 0) return res; + + res = recvfrom(s, buf, len, flags, from, fromlen); + + return res; + } + + int __stdcall send(const SOCKET s, const char* buf, const int len, const int flags) + { + auto server = dw::find_server_by_socket(s); + if (server) return server->send(buf, len); + + return ::send(s, buf, len, flags); + } + + int __stdcall recv(const SOCKET s, char* buf, const int len, const int flags) + { + auto server = dw::find_server_by_socket(s); + if (server) + { + const auto blocking = dw::is_blocking_socket(s, TCP_BLOCKING); + + int result; + do + { + result = server->recv(buf, len); + if (blocking && result < 0) std::this_thread::sleep_for(1ms); + } + while (blocking && result < 0); + + if (!blocking && result < 0) + { + WSASetLastError(WSAEWOULDBLOCK); + } + + return result; + } + + return ::recv(s, buf, len, flags); + } + + int __stdcall connect(const SOCKET s, const sockaddr* addr, const int len) + { + if (len == sizeof(sockaddr_in)) + { + const auto* in_addr = reinterpret_cast(addr); + if (dw::link_socket(s, in_addr->sin_addr.s_addr)) return 0; + } + + return ::connect(s, addr, len); + } + + int __stdcall close_socket(const SOCKET s) + { + dw::remove_blocking_socket(s); + dw::unlink_socket(s); + return closesocket(s); + } + + int __stdcall ioctl_socket(const SOCKET s, const long cmd, u_long* argp) + { + if (static_cast(cmd) == (FIONBIO)) + { + dw::set_blocking_socket(s, *argp == 0); + } + + return ioctlsocket(s, cmd, argp); + } + + hostent* __stdcall get_host_by_name(char* name) + { + static std::mutex mutex; + std::lock_guard _(mutex); + + unsigned long addr = 0; + const auto server = dw::find_server_by_name(name); + if (server) addr = server->get_address(); + + const auto stun_server = dw::find_stun_server_by_name(name); + if (stun_server) addr = stun_server->get_address(); + + if (server || stun_server) + { + static in_addr address; + address.s_addr = addr; + + static in_addr* addr_list[2]; + addr_list[0] = &address; + addr_list[1] = nullptr; + + static hostent host; + host.h_name = name; + host.h_aliases = nullptr; + host.h_addrtype = AF_INET; + host.h_length = sizeof(in_addr); + host.h_addr_list = reinterpret_cast(addr_list); + + return &host; + } + +#pragma warning(push) +#pragma warning(disable: 4996) + + // ReSharper disable once CppDeprecatedEntity + return gethostbyname(name); + +#pragma warning(pop) + } + + bool register_hook(const std::string& process, void* stub) + { + const utils::nt::module main; + + auto result = false; + result = result || utils::hook::iat(main, "wsock32.dll", process, stub); + result = result || utils::hook::iat(main, "WS2_32.dll", process, stub); + return result; + } + } +} + +namespace demonware +{ + bool dw::terminate_; + std::thread dw::message_thread_; + std::recursive_mutex dw::server_mutex_; + std::map dw::blocking_sockets_; + std::map> dw::socket_links_; + std::map> dw::servers_; + std::map> dw::stun_servers_; + std::map>> dw::datagram_packets_; + + uint8_t dw::encryption_key_[24]; + uint8_t dw::decryption_key_[24]; + + std::shared_ptr dw::find_server_by_name(const std::string& name) + { + std::lock_guard _(server_mutex_); + return find_server_by_address(utils::cryptography::jenkins_one_at_a_time::compute(name)); } - void post_load() override + std::shared_ptr dw::find_server_by_address(const unsigned long address) { + std::lock_guard _(server_mutex_); + + const auto server = servers_.find(address); + if (server != servers_.end()) + { + return server->second; + } + + return std::shared_ptr(); + } + + std::shared_ptr dw::find_stun_server_by_name(const std::string& name) + { + std::lock_guard _(server_mutex_); + return find_stun_server_by_address(utils::cryptography::jenkins_one_at_a_time::compute(name)); + } + + std::shared_ptr dw::find_stun_server_by_address(const unsigned long address) + { + std::lock_guard _(server_mutex_); + + const auto server = stun_servers_.find(address); + if (server != stun_servers_.end()) + { + return server->second; + } + + return std::shared_ptr(); + } + + std::shared_ptr dw::find_server_by_socket(const SOCKET s) + { + std::lock_guard _(server_mutex_); + + const auto server = socket_links_.find(s); + if (server != socket_links_.end()) + { + return server->second; + } + + return std::shared_ptr(); + } + + bool dw::link_socket(const SOCKET s, const unsigned long address) + { + std::lock_guard _(server_mutex_); + + const auto server = find_server_by_address(address); + if (!server) return false; + + socket_links_[s] = server; + return true; + } + + void dw::unlink_socket(const SOCKET sock) + { + std::lock_guard _(server_mutex_); + + const auto server = socket_links_.find(sock); + if (server != socket_links_.end()) + { + socket_links_.erase(server); + } + + const auto dgram_packets = datagram_packets_.find(sock); + if (dgram_packets != datagram_packets_.end()) + { + datagram_packets_.erase(dgram_packets); + } + } + + int dw::recv_datagam_packet(const SOCKET s, char* buf, const int len, sockaddr* from, int* fromlen) + { + std::unique_lock lock(server_mutex_); + + auto queue = datagram_packets_.find(s); + if (queue != datagram_packets_.end()) + { + const bool blocking = is_blocking_socket(s, UDP_BLOCKING); + + lock.unlock(); + while (blocking && queue->second.empty()) + { + std::this_thread::sleep_for(1ms); + } + lock.lock(); + + if (!queue->second.empty()) + { + auto packet = queue->second.front(); + queue->second.pop(); + + *fromlen = INT(packet.first.size()); + std::memcpy(from, packet.first.data(), *fromlen); + + const int size = std::min(len, INT(packet.second.size())); + std::memcpy(buf, packet.second.data(), size); + + return size; + } + + WSASetLastError(WSAEWOULDBLOCK); + return -1; + } + + return 0; + } + + void dw::send_datagram_packet(const SOCKET s, const std::string& data, const sockaddr* to, const int tolen) + { + std::lock_guard _(server_mutex_); + datagram_packets_[s].push({std::string(LPSTR(to), tolen), data}); + } + + bool dw::is_blocking_socket(const SOCKET s, const bool def) + { + std::lock_guard _(server_mutex_); + + if (blocking_sockets_.find(s) != blocking_sockets_.end()) + { + return blocking_sockets_[s]; + } + + return def; + } + + void dw::remove_blocking_socket(const SOCKET s) + { + std::lock_guard _(server_mutex_); + + const auto entry = blocking_sockets_.find(s); + if (entry != blocking_sockets_.end()) + { + blocking_sockets_.erase(entry); + } + } + + void dw::set_blocking_socket(const SOCKET s, const bool blocking) + { + std::lock_guard _(server_mutex_); + blocking_sockets_[s] = blocking; + } + + uint8_t* dw::get_key(const bool encrypt) + { + return encrypt ? encryption_key_ : decryption_key_; + } + + void dw::set_key(const bool encrypt, uint8_t* key) + { + static_assert(sizeof encryption_key_ == sizeof decryption_key_); + std::memcpy(encrypt ? encryption_key_ : decryption_key_, key, sizeof encryption_key_); + } + + void dw::server_thread() + { + terminate_ = false; + while (!terminate_) + { + std::unique_lock lock(server_mutex_); + + for (auto& server : servers_) + { + server.second->run_frame(); + } + + lock.unlock(); + + std::this_thread::sleep_for(50ms); + } + } + + void dw::pre_destroy() + { + std::lock_guard _(server_mutex_); + + terminate_ = true; + if (message_thread_.joinable()) + { + message_thread_.join(); + } + + servers_.clear(); + stun_servers_.clear(); + socket_links_.clear(); + blocking_sockets_.clear(); + datagram_packets_.clear(); + } + + dw::dw() + { + register_stun_server("mw3-stun.us.demonware.net"); + register_stun_server("mw3-stun.eu.demonware.net"); + register_stun_server("stun.jp.demonware.net"); + register_stun_server("stun.au.demonware.net"); + register_stun_server("stun.eu.demonware.net"); + register_stun_server("stun.us.demonware.net"); + + auto lsg_server = register_server("mw3-pc-lobby.prod.demonware.net"); + auto auth_server = register_server("mw3-pc-auth.prod.demonware.net"); + + auth_server->register_service(); + + lsg_server->register_service(); + lsg_server->register_service(); + lsg_server->register_service(); + lsg_server->register_service(); + /*lsg_server->register_service(); + lsg_server->register_service(); + lsg_server->register_service(); + lsg_server->register_service(); + lsg_server->register_service();*/ + } + + void dw::post_load() + { + message_thread_ = std::thread(server_thread); + + io::register_hook("send", io::send); + io::register_hook("recv", io::recv); + io::register_hook("sendto", io::send_to); + io::register_hook("recvfrom", io::recv_from); + io::register_hook("connect", io::connect); + io::register_hook("closesocket", io::close_socket); + io::register_hook("ioctlsocket", io::ioctl_socket); + io::register_hook("gethostbyname", io::get_host_by_name); + utils::hook(SELECT_VALUE(0x6F40A0, 0x6EE1C0, 0x611310), bd_logger_stub, HOOK_JUMP).install()->quick(); } -private: - static void bd_logger_stub(int /*type*/, const char* const /*channelName*/, const char*, const char* const /*file*/, - const char* const function, const unsigned int /*line*/, const char* const msg, ...) + void dw::bd_logger_stub(int /*type*/, const char* const /*channelName*/, const char*, const char* const /*file*/, + const char* const function, const unsigned int /*line*/, const char* const msg, ...) { char buffer[2048]; @@ -30,6 +417,6 @@ private: va_end(ap); } -}; -REGISTER_MODULE(dw) + REGISTER_MODULE(dw) +} diff --git a/src/module/dw.hpp b/src/module/dw.hpp new file mode 100644 index 0000000..4f0f6f5 --- /dev/null +++ b/src/module/dw.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "loader/module_loader.hpp" +#include "game/demonware/stun_server.hpp" +#include "game/demonware/service_server.hpp" + +#define TCP_BLOCKING true +#define UDP_BLOCKING false + +namespace demonware +{ + class dw final : public module + { + public: + dw(); + + void post_load() override; + void pre_destroy() override; + + template + static std::shared_ptr register_server(Args ... args) + { + std::lock_guard _(server_mutex_); + auto server = std::make_shared(args...); + servers_[server->get_address()] = server; + return server; + } + + static std::shared_ptr register_stun_server(std::string name) + { + std::lock_guard _(server_mutex_); + auto server = std::make_shared(name); + stun_servers_[server->get_address()] = server; + return server; + } + + static int recv_datagam_packet(SOCKET s, char* buf, int len, sockaddr* from, int* fromlen); + static void send_datagram_packet(SOCKET s, const std::string& data, const sockaddr* to, int tolen); + + static bool is_blocking_socket(SOCKET s, bool def); + static void remove_blocking_socket(SOCKET s); + static void set_blocking_socket(SOCKET s, bool blocking); + + static std::shared_ptr find_stun_server_by_name(const std::string& name); + static std::shared_ptr find_stun_server_by_address(unsigned long address); + + static std::shared_ptr find_server_by_name(const std::string& name); + static std::shared_ptr find_server_by_address(unsigned long address); + static std::shared_ptr find_server_by_socket(SOCKET s); + static bool link_socket(SOCKET sock, unsigned long address); + static void unlink_socket(SOCKET sock); + + static void set_key(bool encrypt, uint8_t* key); + static uint8_t* get_key(bool encrypt); + + private: + static bool terminate_; + static std::thread message_thread_; + static std::recursive_mutex server_mutex_; + + static uint8_t encryption_key_[24]; + static uint8_t decryption_key_[24]; + + static std::map blocking_sockets_; + static std::map> socket_links_; + static std::map> servers_; + static std::map> stun_servers_; + static std::map>> datagram_packets_; + + static void server_thread(); + + static void bd_logger_stub(int /*type*/, const char* const /*channelName*/, const char*, + const char* const /*file*/, + const char* const function, const unsigned int /*line*/, const char* const msg, ...); + }; +} diff --git a/src/resource.hpp b/src/resource.hpp index 26728e0..bbb6e8b 100644 --- a/src/resource.hpp +++ b/src/resource.hpp @@ -4,3 +4,12 @@ #define IMAGE_MP 301 #define BINARY_SP 302 #define BINARY_MP 303 + +#define DW_HEATMAP 304 +#define DW_MOTD 305 +#define DW_IMG 306 +#define DW_WAD 307 +#define DW_PLAYLIST 308 +#define DW_CONFIG 309 +#define DW_IOTD_TXT 310 +#define DW_IOTD_IMG 311 diff --git a/src/resource.rc b/src/resource.rc index cfd0da9..52a93bb 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -92,6 +92,15 @@ IMAGE_MP BITMAP "resources/multiplayer.bmp" BINARY_SP RCDATA "resources/iw5sp.exe.diff" BINARY_MP RCDATA "resources/iw5mp.exe.diff" +DW_HEATMAP RCDATA "resources/dw/heatmap.raw" +DW_MOTD RCDATA "resources/dw/motd-english.txt" +DW_IMG RCDATA "resources/dw/online_mp.img" +DW_WAD RCDATA "resources/dw/online_tu14_mp_english.wad" +DW_PLAYLIST RCDATA "resources/dw/playlists.aggr" +DW_CONFIG RCDATA "resources/dw/social_tu1.cfg" +DW_IOTD_TXT RCDATA "resources/dw/iotd-english.txt" +DW_IOTD_IMG RCDATA "resources/dw/iotd-english.jpg" + #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/resources/dw/heatmap.raw b/src/resources/dw/heatmap.raw new file mode 100644 index 0000000000000000000000000000000000000000..20d9ccb4b242875388ca70b0f33258d15082370b GIT binary patch literal 91 pcmb=p_4WcIBLf2mlfvKnTa3OOY7-bp1|R%IQS=;VFzIAq0su+F33vbi literal 0 HcmV?d00001 diff --git a/src/resources/dw/iotd-english.jpg b/src/resources/dw/iotd-english.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c88fc330ea8fa5f68bb54ae97fc2bf0aa410b621 GIT binary patch literal 17281 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

)SYes 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 literal 0 HcmV?d00001 diff --git a/src/resources/dw/iotd-english.txt b/src/resources/dw/iotd-english.txt new file mode 100644 index 0000000..e841c8a --- /dev/null +++ b/src/resources/dw/iotd-english.txt @@ -0,0 +1 @@ +Blub \ No newline at end of file diff --git a/src/resources/dw/motd-english.txt b/src/resources/dw/motd-english.txt new file mode 100644 index 0000000..103fd1a --- /dev/null +++ b/src/resources/dw/motd-english.txt @@ -0,0 +1 @@ +Welcome to Open-IW5 \ No newline at end of file diff --git a/src/resources/dw/online_mp.img b/src/resources/dw/online_mp.img new file mode 100644 index 0000000000000000000000000000000000000000..ebc6122d5b8875ae865124cec03d60094d5b0e55 GIT binary patch literal 10240 zcmeHN&2Aev5Kh~h0iXH;L@v45u4OAu+M8q9O@O#IEG0pK01Ik4+GV{<(j?cC@@hTw zk@^Z9{_JXZEwxBbEwBa*n4ICv@SFK&DBB+|?@qpX_Uzer`28(@tAefzZmda4b;QL& z;@ATS^>ATS^>ATS^>ATS^>ATS^>ATS^>ATS^>ATS^>ATS^>ATS{C|3cu; z-~V_7ZMYQVZ1ie>Z+}l$1rSBGT_+hWVQTTm881P<2a(yFyo@R))yYbkl%o2$wWq<@ zl}bR+IZBSBM8iYHHQXo=zfem?9X}-(g`CgV7Tu83`iKGJ;z}sP2rV~oMR@^i3RI{0 zg{%acsdZCXCM8`hlxaMg$3%c#N`0TW?a?vn8nDF0O}S=Tszg-xKE(ch2^oDNbjmJ5 z?cKT~^DJ4=qL|Qh%i`9t%EKkfjPL{F^7`7K}cHMTxgy;W%BX} zQ|r{S#H>Y{Ybj)9E(#7}TXk4`$5Z4@Ukud*IcMrkhWmDEeB z*)08X;ROUDQ68+k}IT9^p9%TXn)p7KL+mv<0-XLdq6imwnuG| z0Xd0ChKAt1oHMM?Zt^HImn&{3TiB^9F~8qk*`XdTaAIkSeGxm!wsM^I(|>$)c7|i` zN&U1Ojb2Af#!6*ebJ<)JvxzlqKXIGiIH+ep%YHsD1(fTn$j#6K+a1}Z{EjlJKPW~m zgo-|jXvLDaLfZCqPHG*Ud=WSk$+)+HJx}O#H`u2c{?8KDX zMc?8DRkqT2>m0~cL9>ljue?1nDPV!ReOosI4xJ5hF}vE>X_Q;GnrNdi!EPa#S-Q>? zDBGliA*|hOOrVqncQbwC#A&tcx6)~zoDVN7f>Q!>YOI#)jUA`G-A?|{kK-+Nl7|X= z4z{3;g?sEtyKETKw=u;Dp-hhK0ll%|^jcR)H85_UoZVdvts68*cj^MWQpa_x$@%Pjf6t8`_dhjcL{NzIA$9WlHg9-A?zpQl zhEu~a$UV#&nw^u9DuO$d+*SBErh-U3p?P-nS$q~r^YvNW`uC9eiHr|NCupz27j}BT zQjW{mzx|kS@0-?ZAGsHXq-oysVVXGoU=H7P-6M|ez=AXlPjZJGMukBg;8hP0$+$wg z9-zm#LSDCI$fedQ7JX}nvDU)07x_b5V%(yI_{l#Z{-(kQh$b@}_PbD@A@hy<6h!30 zw}BdaE*`1-gt8m_vws7AFDqXSejV0<0~}{tvv%oduU!h-YnO!f8bQZcn&ZYC;3tjE zGDw%>B+}3O!}@5!|CBTmAKqkqybJ5|0(tHy069fiZ#7uECS`?P@5Nje_K=3pDpC~d r7mf)xfib&NFg|>}tNigr^Ofa~_ivy0`|9@{;o$7Q`}=_JF|@w`=Z8>` literal 0 HcmV?d00001 diff --git a/src/resources/dw/online_tu14_mp_english.wad b/src/resources/dw/online_tu14_mp_english.wad new file mode 100644 index 0000000000000000000000000000000000000000..b7d22fbc7be1cc6a5e708121a9e73e402a0a37a9 GIT binary patch literal 24633 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

6ohUzm9PG@_eUI|T3Bj<>i<6TT*@p*x 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 dtj9W^eU(E^o>6fSb_q_EpOUy7;T_gKO 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 Md$P0w00*WLE%fC{3IG5A literal 0 HcmV?d00001 diff --git a/src/resources/dw/social_tu1.cfg b/src/resources/dw/social_tu1.cfg new file mode 100644 index 0000000..0b6b433 --- /dev/null +++ b/src/resources/dw/social_tu1.cfg @@ -0,0 +1,55 @@ +// DW Delay values +set facebook_active 0 +set facebook_friends_active 0 +set facebook_upload_video_active 0 +set facebook_upload_photo_active 0 +set facebook_delay 750 +set facebook_max_retry_time 30000 +set facebook_retry_step 1000 +set facebook_friends_max_retry_time 30000 +set facebook_friends_retry_step 1000 + +set entitlements_active 0 +set entitlements_delay 500 +set entitlements_config_file_max_retry_time 30000 +set entitlements_config_file_retry_step 1000 +set entitlements_key_archive_max_retry_time 30000 +set entitlements_key_archive_retry_step 1000 + +set userGroup_active 1 +set elite_clan_active 0 + +set cl_enableDedicatedServerBrowser 1 + +set dw_presence_active 0 +set dw_presence_put_delay 5000 +set dw_presence_put_rate 30000 +set dw_presence_get_delay 5000 +set dw_presence_get_rate 60000 + +#ifdef MP +set onlinevault_active 1 +set onlinevault_maxslots_sub0 10 +set onlinevault_maxslots_sub1 10 +set onlinevault_maxslots_sub2 10 +set onlinevault_maxtime_sub0 30 +set onlinevault_maxtime_sub1 30 +set onlinevault_maxtime_sub2 60 +#endif // #ifdef MP + +set past_title_data_active 0 + +set pm_gamesetup_mode_altmodes 1 +set pm_gamesetup_mode_altmodes_dropzone 1 +set pm_gamesetup_mode_altmodes_teamjug 1 +set pm_gamesetup_mode_altmodes_jug 1 +set pm_gamesetup_mode_altmodes_gungame 1 +set pm_gamesetup_mode_altmodes_infected 1 +set pm_gamesetup_mode_altmodes_oneinthechamber 1 + +set pm_gamesetup_options_createdefaultclass 1 +set pm_gamesetup_options_customclassrestrictions 1 + +set prestige_shop_active 1 + +set theater_active 1 diff --git a/src/std_include.hpp b/src/std_include.hpp index 55e8637..10c5560 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +42,8 @@ #include #pragma comment (lib, "gdiplus.lib") +#pragma comment(lib, "ws2_32.lib") + #pragma warning(pop) #pragma warning(disable: 4100) diff --git a/src/steam/interfaces/user.cpp b/src/steam/interfaces/user.cpp index a83ab5a..25d310a 100644 --- a/src/steam/interfaces/user.cpp +++ b/src/steam/interfaces/user.cpp @@ -1,8 +1,11 @@ #include #include "steam/steam.hpp" +#include "module/dw.hpp" namespace steam { + std::string auth_ticket; + int user::GetHSteamUser() { return NULL; @@ -107,7 +110,12 @@ namespace steam unsigned __int64 user::RequestEncryptedAppTicket(void* pUserData, int cbUserData) { // Generate the authentication ticket - //Components::DemonWare::GenerateAuthTicket(std::string(reinterpret_cast(pUserData), cbUserData)); + const auto id = this->GetSteamID(); + + auth_ticket = "Open-IW5"; + auth_ticket.resize(32); + auth_ticket.append(reinterpret_cast(pUserData), cbUserData); + auth_ticket.append(reinterpret_cast(&id.bits), sizeof(id.bits)); // Create the call response const auto result = callbacks::register_call(); @@ -124,8 +132,12 @@ namespace steam bool user::GetEncryptedAppTicket(void* pTicket, int cbMaxTicket, unsigned int* pcbTicket) { - if (cbMaxTicket < 0) return false; - return false; - //Components::DemonWare::GetAuthTicket(pTicket, static_cast(cbMaxTicket), pcbTicket); + if (cbMaxTicket < 0 || auth_ticket.empty()) return false; + + const auto size = std::min(size_t(cbMaxTicket), auth_ticket.size()); + std::memcpy(pTicket, auth_ticket.data(), size); + *pcbTicket = size; + + return true; } } diff --git a/src/utils/hook.cpp b/src/utils/hook.cpp index a486f9e..50c52f2 100644 --- a/src/utils/hook.cpp +++ b/src/utils/hook.cpp @@ -96,8 +96,9 @@ namespace utils *reinterpret_cast(code + 1) = reinterpret_cast(this->stub_) - (reinterpret_cast(this-> place_) + 5); - if (unprotect && !keep_unprotected) VirtualProtect(this->place_, sizeof(this->buffer_), this->protection_, - &this->protection_); + if (unprotect && !keep_unprotected) + VirtualProtect(this->place_, sizeof(this->buffer_), this->protection_, + &this->protection_); FlushInstructionCache(GetCurrentProcess(), this->place_, sizeof(this->buffer_)); @@ -112,6 +113,22 @@ namespace utils } } + bool hook::iat(nt::module module, const std::string& target_module, const std::string& process, void* stub) + { + if (!module.is_valid()) return false; + + auto ptr = module.get_iat_entry(target_module, process); + if (!ptr) return false; + + DWORD protect; + VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect); + + *ptr = stub; + + VirtualProtect(ptr, sizeof(*ptr), protect, &protect); + return true; + } + hook* hook::uninstall(const bool unprotect) { std::lock_guard _(this->state_mutex_); diff --git a/src/utils/hook.hpp b/src/utils/hook.hpp index fc3b3c4..448b9b4 100644 --- a/src/utils/hook.hpp +++ b/src/utils/hook.hpp @@ -1,4 +1,5 @@ #pragma once +#include "nt.hpp" #define HOOK_JUMP true #define HOOK_CALL false @@ -81,6 +82,8 @@ namespace utils void* get_address() const; void quick(); + static bool iat(nt::module module, const std::string& target_module, const std::string& process, void* stub); + static void nop(void* place, size_t length); static void nop(DWORD place, size_t length);