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 0000000..20d9ccb Binary files /dev/null and b/src/resources/dw/heatmap.raw differ diff --git a/src/resources/dw/iotd-english.jpg b/src/resources/dw/iotd-english.jpg new file mode 100644 index 0000000..c88fc33 Binary files /dev/null and b/src/resources/dw/iotd-english.jpg differ 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 0000000..ebc6122 Binary files /dev/null and b/src/resources/dw/online_mp.img differ 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 0000000..b7d22fb Binary files /dev/null and b/src/resources/dw/online_tu14_mp_english.wad differ diff --git a/src/resources/dw/playlists.aggr b/src/resources/dw/playlists.aggr new file mode 100644 index 0000000..8bcff06 Binary files /dev/null and b/src/resources/dw/playlists.aggr differ 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);