From e1333db8a2c9c9e2c7ab55a18bd5d54b8a957926 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Wed, 26 Dec 2018 20:21:20 +0100 Subject: [PATCH] Add console handling and prepare demonware emulation --- premake5.lua | 2 +- src/game/demonware/bit_buffer.cpp | 182 +++++++++++ src/game/demonware/bit_buffer.hpp | 40 +++ src/game/demonware/byte_buffer.cpp | 298 +++++++++++++++++ src/game/demonware/byte_buffer.hpp | 66 ++++ src/game/demonware/data_types.hpp | 449 ++++++++++++++++++++++++++ src/game/demonware/i_server.hpp | 182 +++++++++++ src/game/demonware/i_service.hpp | 70 ++++ src/game/demonware/service_server.cpp | 196 +++++++++++ src/game/demonware/service_server.hpp | 42 +++ src/game/demonware/stun_server.cpp | 70 ++++ src/game/demonware/stun_server.hpp | 21 ++ src/game/game.cpp | 4 + src/game/game.hpp | 10 +- src/game/structs.hpp | 15 + src/launcher/image.cpp | 4 +- src/launcher/launcher.cpp | 2 +- src/loader/loader.cpp | 2 +- src/loader/module.hpp | 4 + src/loader/module_loader.cpp | 12 + src/loader/module_loader.hpp | 1 + src/main.cpp | 6 +- src/module/console.cpp | 91 +++++- src/module/console.hpp | 1 - src/module/dw.cpp | 22 ++ src/std_include.hpp | 5 + src/utils/cryptography.cpp | 2 +- 27 files changed, 1786 insertions(+), 13 deletions(-) create mode 100644 src/game/demonware/bit_buffer.cpp create mode 100644 src/game/demonware/bit_buffer.hpp create mode 100644 src/game/demonware/byte_buffer.cpp create mode 100644 src/game/demonware/byte_buffer.hpp create mode 100644 src/game/demonware/data_types.hpp create mode 100644 src/game/demonware/i_server.hpp create mode 100644 src/game/demonware/i_service.hpp create mode 100644 src/game/demonware/service_server.cpp create mode 100644 src/game/demonware/service_server.hpp create mode 100644 src/game/demonware/stun_server.cpp create mode 100644 src/game/demonware/stun_server.hpp create mode 100644 src/game/structs.hpp delete mode 100644 src/module/console.hpp diff --git a/premake5.lua b/premake5.lua index 3f81378..612d057 100644 --- a/premake5.lua +++ b/premake5.lua @@ -91,7 +91,7 @@ workspace "open-iw5" configuration {} project "open-iw5" - kind "WindowedApp" + kind "ConsoleApp" language "C++" pchheader "std_include.hpp" diff --git a/src/game/demonware/bit_buffer.cpp b/src/game/demonware/bit_buffer.cpp new file mode 100644 index 0000000..9b2c458 --- /dev/null +++ b/src/game/demonware/bit_buffer.cpp @@ -0,0 +1,182 @@ +#include +#include "bit_buffer.hpp" + +namespace demonware +{ + bool bit_buffer::read_bytes(const unsigned int bytes, unsigned char* output) + { + return this->read(bytes * 8, output); + } + + bool bit_buffer::read_bool(bool* output) + { + if (!this->read_data_type(1)) + { + return false; + } + + return this->read(1, output); + } + + bool bit_buffer::read_u_int32(unsigned int* output) + { + if (!this->read_data_type(8)) + { + return false; + } + + return this->read(32, output); + } + + bool bit_buffer::read_data_type(const char expected) + { + char data_type = 0; + + if (!this->use_data_types_) return true; + if (this->read(5, &data_type)) + { + return (data_type == expected); + } + + return false; + } + + bool bit_buffer::write_bytes(const unsigned int bytes, const char* data) + { + return this->write_bytes(bytes, reinterpret_cast(data)); + } + + bool bit_buffer::write_bytes(unsigned int bytes, const unsigned char* data) + { + return this->write(bytes * 8, data); + } + + bool bit_buffer::write_bool(bool data) + { + if (this->write_data_type(1)) + { + return this->write(1, &data); + } + + return false; + } + + bool bit_buffer::write_int32(int data) + { + if (this->write_data_type(7)) + { + return this->write(32, &data); + } + + return false; + } + + bool bit_buffer::write_uint32(unsigned int data) + { + if (this->write_data_type(8)) + { + return this->write(32, &data); + } + + return false; + } + + bool bit_buffer::write_data_type(char data) + { + if (!this->use_data_types_) + { + return true; + } + + return this->write(5, &data); + } + + bool bit_buffer::read(unsigned int bits, void* output) + { + if (bits == 0) return false; + if ((this->current_bit_ + bits) > (this->buffer_.size() * 8)) return false; + + int cur_byte = this->current_bit_ >> 3; + auto cur_out = 0; + + const char* bytes = this->buffer_.data(); + const auto output_bytes = reinterpret_cast(output); + + while (bits > 0) + { + const int min_bit = (bits < 8) ? bits : 8; + const auto this_byte = bytes[cur_byte++] & 0xFF; + const int remain = this->current_bit_ & 7; + + if ((min_bit + remain) <= 8) + { + output_bytes[cur_out] = BYTE((0xFF >> (8 - min_bit)) & (this_byte >> remain)); + } + else + { + output_bytes[cur_out] = BYTE( + (0xFF >> (8 - min_bit)) & (bytes[cur_byte] << (8 - remain)) | (this_byte >> remain)); + } + + cur_out++; + this->current_bit_ += min_bit; + bits -= min_bit; + } + + return true; + } + + bool bit_buffer::write(const unsigned int bits, const void* data) + { + if (bits == 0) return false; + this->buffer_.resize(this->buffer_.size() + (bits >> 3) + 1); + + int bit = bits; + const auto bytes = const_cast(this->buffer_.data()); + const auto* input_bytes = reinterpret_cast(data); + + while (bit > 0) + { + const int bit_pos = this->current_bit_ & 7; + auto rem_bit = 8 - bit_pos; + const auto this_write = (bit < rem_bit) ? bit : rem_bit; + + const BYTE mask = ((0xFF >> rem_bit) | (0xFF << (bit_pos + this_write))); + const int byte_pos = this->current_bit_ >> 3; + + const BYTE temp_byte = (mask & bytes[byte_pos]); + const BYTE this_bit = ((bits - bit) & 7); + const auto this_byte = (bits - bit) >> 3; + + auto this_data = input_bytes[this_byte]; + + const auto next_byte = (((bits - 1) >> 3) > this_byte) ? input_bytes[this_byte + 1] : 0; + + this_data = BYTE((next_byte << (8 - this_bit)) | (this_data >> this_bit)); + + const BYTE out_byte = (~mask & (this_data << bit_pos) | temp_byte); + bytes[byte_pos] = out_byte; + + this->current_bit_ += this_write; + bit -= this_write; + } + + return true; + } + + void bit_buffer::set_use_data_types(const bool use_data_types) + { + this->use_data_types_ = use_data_types; + } + + unsigned int bit_buffer::size() const + { + return this->current_bit_ / 8 + (this->current_bit_ % 8 ? 1 : 0); + } + + std::string& bit_buffer::get_buffer() + { + this->buffer_.resize(this->size()); + return this->buffer_; + } +} diff --git a/src/game/demonware/bit_buffer.hpp b/src/game/demonware/bit_buffer.hpp new file mode 100644 index 0000000..0de889e --- /dev/null +++ b/src/game/demonware/bit_buffer.hpp @@ -0,0 +1,40 @@ +#pragma once + +namespace demonware +{ + class bit_buffer final + { + public: + bit_buffer() = default; + + explicit bit_buffer(std::string buffer) : buffer_(std::move(buffer)) + { + } + + bool read_bytes(unsigned int bytes, unsigned char* output); + bool read_bool(bool* output); + bool read_u_int32(unsigned int* output); + bool read_data_type(char expected); + + bool write_bytes(unsigned int bytes, const char* data); + bool write_bytes(unsigned int bytes, const unsigned char* data); + bool write_bool(bool data); + bool write_int32(int data); + bool write_uint32(unsigned int data); + bool write_data_type(char data); + + bool read(unsigned int bits, void* output); + bool write(unsigned int bits, const void* data); + + void set_use_data_types(bool use_data_types); + + unsigned int size() const; + + std::string& get_buffer(); + + private: + std::string buffer_{}; + unsigned int current_bit_ = 0; + bool use_data_types_ = true; + }; +} diff --git a/src/game/demonware/byte_buffer.cpp b/src/game/demonware/byte_buffer.cpp new file mode 100644 index 0000000..7c6ea85 --- /dev/null +++ b/src/game/demonware/byte_buffer.cpp @@ -0,0 +1,298 @@ +#include +#include "byte_buffer.hpp" + +namespace demonware +{ + bool byte_buffer::read_byte(unsigned char* output) + { + if (!this->read_data_type(3)) return false; + return this->read(1, output); + } + + bool byte_buffer::read_bool(bool* output) + { + if (!this->read_data_type(1)) return false; + return this->read(1, output); + } + + bool byte_buffer::read_int16(short* output) + { + if (!this->read_data_type(5)) return false; + return this->read(2, output); + } + + bool byte_buffer::read_uint16(unsigned short* output) + { + if (!this->read_data_type(6)) return false; + return this->read(2, output); + } + + bool byte_buffer::read_int32(int* output) + { + if (!this->read_data_type(7)) return false; + return this->read(4, output); + } + + bool byte_buffer::read_uint32(unsigned int* output) + { + if (!this->read_data_type(8)) return false; + return this->read(4, output); + } + + bool byte_buffer::read_int64(__int64* output) + { + if (!this->read_data_type(9)) return false; + return this->read(8, output); + } + + bool byte_buffer::read_uint64(unsigned __int64* output) + { + if (!this->read_data_type(10)) return false; + return this->read(8, output); + } + + bool byte_buffer::read_float(float* output) + { + if (!this->read_data_type(13)) return false; + return this->read(4, output); + } + + bool byte_buffer::read_string(std::string* output) + { + char* out_data; + if (this->read_string(&out_data)) + { + output->clear(); + output->append(out_data); + return true; + } + + return false; + } + + bool byte_buffer::read_string(char** output) + { + if (!this->read_data_type(16)) return false; + + *output = const_cast(this->buffer_.data()) + this->current_byte_; + this->current_byte_ += strlen(*output) + 1; + + return true; + } + + bool byte_buffer::read_string(char* output, const int length) + { + if (!this->read_data_type(16)) return false; + + strcpy_s(output, length, const_cast(this->buffer_.data()) + this->current_byte_); + this->current_byte_ += strlen(output) + 1; + + return true; + } + + bool byte_buffer::read_blob(std::string* output) + { + char* out_data; + int length; + if (this->read_blob(&out_data, &length)) + { + output->clear(); + output->append(out_data, length); + return true; + } + + return false; + } + + bool byte_buffer::read_blob(char** output, int* length) + { + if (!this->read_data_type(0x13)) + { + return false; + } + + unsigned int size; + this->read_uint32(&size); + + *output = const_cast(this->buffer_.data()) + this->current_byte_; + *length = static_cast(size); + + this->current_byte_ += size; + + return true; + } + + bool byte_buffer::read_data_type(char expected) + { + if (!this->use_data_types_) return true; + + char type; + this->read(1, &type); + return type == expected; + } + + bool byte_buffer::read_array_header(const unsigned char expected, unsigned int* element_count, + unsigned int* element_size) + { + if (element_count) *element_count = 0; + if (element_size) *element_size = 0; + + if (!this->read_data_type(expected + 100)) return false; + + uint32_t array_size, el_count; + if (!this->read_uint32(&array_size)) return false; + + this->set_use_data_types(false); + this->read_uint32(&el_count); + this->set_use_data_types(true); + + if (element_count) *element_count = el_count; + if (element_size) *element_size = array_size / el_count; + + return true; + } + + bool byte_buffer::write_byte(char data) + { + this->write_data_type(3); + return this->write(1, &data); + } + + bool byte_buffer::write_bool(bool data) + { + this->write_data_type(1); + return this->write(1, &data); + } + + bool byte_buffer::write_int16(short data) + { + this->write_data_type(5); + return this->write(2, &data); + } + + bool byte_buffer::write_uint16(unsigned short data) + { + this->write_data_type(6); + return this->write(2, &data); + } + + bool byte_buffer::write_int32(int data) + { + this->write_data_type(7); + return this->write(4, &data); + } + + bool byte_buffer::write_uint32(unsigned int data) + { + this->write_data_type(8); + return this->write(4, &data); + } + + bool byte_buffer::write_int64(__int64 data) + { + this->write_data_type(9); + return this->write(8, &data); + } + + bool byte_buffer::write_uint64(unsigned __int64 data) + { + this->write_data_type(10); + return this->write(8, &data); + } + + bool byte_buffer::write_data_type(char data) + { + if (!this->use_data_types_) return true; + return this->write(1, &data); + } + + bool byte_buffer::write_float(float data) + { + this->write_data_type(13); + return this->write(4, &data); + } + + bool byte_buffer::write_string(const std::string& data) + { + return this->write_string(data.data()); + } + + bool byte_buffer::write_string(const char* data) + { + this->write_data_type(16); + return this->write(static_cast(strlen(data)) + 1, data); + } + + bool byte_buffer::write_blob(const std::string& data) + { + return this->write_blob(data.data(), INT(data.size())); + } + + bool byte_buffer::write_blob(const char* data, const int length) + { + this->write_data_type(0x13); + this->write_uint32(length); + + return this->write(length, data); + } + + bool byte_buffer::write_array_header(const unsigned char type, const unsigned int element_count, + const unsigned int element_size) + { + const auto using_types = this->is_using_data_types(); + this->set_use_data_types(false); + + auto result = this->write_byte(type + 100); + + this->set_use_data_types(true); + result &= this->write_uint32(element_count * element_size); + this->set_use_data_types(false); + + result &= this->write_uint32(element_count); + + this->set_use_data_types(using_types); + return result; + } + + bool byte_buffer::read(int bytes, void* output) + { + if (bytes + this->current_byte_ > this->buffer_.size()) return false; + + std::memmove(output, this->buffer_.data() + this->current_byte_, bytes); + this->current_byte_ += bytes; + + return true; + } + + bool byte_buffer::write(const int bytes, const void* data) + { + this->buffer_.append(reinterpret_cast(data), bytes); + this->current_byte_ += bytes; + return true; + } + + bool byte_buffer::write(const std::string& data) + { + return this->write(data.size(), data.data()); + } + + void byte_buffer::set_use_data_types(bool _useDataTypes) + { + this->use_data_types_ = _useDataTypes; + } + + size_t byte_buffer::size() const + { + return this->buffer_.size(); + } + + bool byte_buffer::is_using_data_types() const + { + return use_data_types_; + } + + std::string& byte_buffer::get_buffer() + { + return this->buffer_; + } +} diff --git a/src/game/demonware/byte_buffer.hpp b/src/game/demonware/byte_buffer.hpp new file mode 100644 index 0000000..84f0fc6 --- /dev/null +++ b/src/game/demonware/byte_buffer.hpp @@ -0,0 +1,66 @@ +#pragma once + +namespace demonware +{ + class byte_buffer final + { + public: + byte_buffer() = default; + + explicit byte_buffer(std::string buffer) : buffer_(std::move(buffer)) + { + } + + bool read_byte(unsigned char* output); + bool read_bool(bool* output); + bool read_int16(short* output); + bool read_uint16(unsigned short* output); + bool read_int32(int* output); + bool read_uint32(unsigned int* output); + bool read_int64(__int64* output); + bool read_uint64(unsigned __int64* output); + bool read_float(float* output); + bool read_string(char** output); + bool read_string(char* output, int length); + bool read_string(std::string* output); + bool read_blob(char** output, int* length); + bool read_blob(std::string* output); + bool read_data_type(char expected); + + bool read_array_header(unsigned char expected, unsigned int* element_count, + unsigned int* element_size = nullptr); + + bool write_byte(char data); + bool write_bool(bool data); + bool write_int16(short data); + bool write_uint16(unsigned short data); + bool write_int32(int data); + bool write_uint32(unsigned int data); + bool write_int64(__int64 data); + bool write_uint64(unsigned __int64 data); + bool write_data_type(char data); + bool write_float(float data); + bool write_string(const char* data); + bool write_string(const std::string& data); + bool write_blob(const char* data, int length); + bool write_blob(const std::string& data); + + bool write_array_header(unsigned char type, unsigned int element_count, unsigned int element_size); + + bool read(int bytes, void* output); + bool write(int bytes, const void* data); + bool write(const std::string& data); + + void set_use_data_types(bool use_data_types); + size_t size() const; + + bool is_using_data_types() const; + + std::string& get_buffer(); + + private: + std::string buffer_; + size_t current_byte_ = 0; + bool use_data_types_ = true; + }; +} diff --git a/src/game/demonware/data_types.hpp b/src/game/demonware/data_types.hpp new file mode 100644 index 0000000..1537dbd --- /dev/null +++ b/src/game/demonware/data_types.hpp @@ -0,0 +1,449 @@ +#pragma once +#include "i_server.hpp" +#include "game/structs.hpp" + +namespace demonware +{ + class bdFileData final : public i_serializable + { + public: + std::string file_data; + + explicit bdFileData(const std::string& buffer) : file_data(buffer) + { + } + + void serialize(byte_buffer* buffer) override + { + buffer->write_blob(this->file_data); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_blob(&this->file_data); + } + }; + + class bdFileInfo final : public i_serializable + { + public: + uint64_t file_id; + uint32_t create_time; + uint32_t modified_time; + bool priv; + uint64_t owner_id; + std::string filename; + uint32_t file_size; + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint32(this->file_size); + buffer->write_uint64(this->file_id); + buffer->write_uint32(this->create_time); + buffer->write_uint32(this->modified_time); + buffer->write_bool(this->priv); + buffer->write_uint64(this->owner_id); + buffer->write_string(this->filename); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint32(&this->file_size); + buffer->read_uint64(&this->file_id); + buffer->read_uint32(&this->create_time); + buffer->read_uint32(&this->modified_time); + buffer->read_bool(&this->priv); + buffer->read_uint64(&this->owner_id); + buffer->read_string(&this->filename); + } + }; + + class bdGroupCount final : public i_serializable + { + public: + uint32_t group_id; + uint32_t group_count; + + bdGroupCount() + { + this->group_id = 0; + this->group_count = 0; + } + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint32(this->group_id); + buffer->write_uint32(this->group_count); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint32(&this->group_id); + buffer->read_uint32(&this->group_count); + } + }; + + class bdTimeStamp final : public i_serializable + { + public: + uint32_t unix_time; + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint32(this->unix_time); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint32(&this->unix_time); + } + }; + + class bdDMLInfo : public i_serializable + { + public: + std::string country_code; // Char [3] + std::string country; // Char [65] + std::string region; // Char [65] + std::string city; // Char [129] + float latitude; + float longitude; + + void serialize(byte_buffer* buffer) override + { + buffer->write_string(this->country_code); + buffer->write_string(this->country); + buffer->write_string(this->region); + buffer->write_string(this->city); + buffer->write_float(this->latitude); + buffer->write_float(this->longitude); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_string(&this->country_code); + buffer->read_string(&this->country); + buffer->read_string(&this->region); + buffer->read_string(&this->city); + buffer->read_float(&this->latitude); + buffer->read_float(&this->longitude); + } + }; + + class bdDMLRawData final : public bdDMLInfo + { + public: + uint32_t asn; // Autonomous System Number. + std::string timezone; + + void serialize(byte_buffer* buffer) override + { + bdDMLInfo::serialize(buffer); + + buffer->write_uint32(this->asn); + buffer->write_string(this->timezone); + } + + void deserialize(byte_buffer* buffer) override + { + bdDMLInfo::deserialize(buffer); + + buffer->read_uint32(&this->asn); + buffer->read_string(&this->timezone); + } + }; + + class bdSessionID final : public i_serializable + { + public: + uint64_t session_id; + + void serialize(byte_buffer* buffer) override + { + buffer->write_blob(LPSTR(&this->session_id), sizeof this->session_id); + } + + void deserialize(byte_buffer* buffer) override + { + int size; + char* data; + buffer->read_blob(&data, &size); + + if (data && size >= sizeof this->session_id) + { + this->session_id = *reinterpret_cast(data); + } + } + }; + + class bdMatchmakingInfo : public i_serializable + { + public: + bdSessionID session_id; + std::string host_addr; + uint32_t game_type; + uint32_t max_players; + uint32_t num_players; + + bool symmetric = false; + + void serialize(byte_buffer* buffer) override + { + buffer->write_blob(this->host_addr); + this->session_id.serialize(buffer); + buffer->write_uint32(this->game_type); + buffer->write_uint32(this->max_players); + buffer->write_uint32(this->num_players); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_blob(&this->host_addr); + + if (this->symmetric) this->session_id.deserialize(buffer); + + buffer->read_uint32(&this->game_type); + buffer->read_uint32(&this->max_players); + + if (this->symmetric) buffer->read_uint32(&this->num_players); + } + }; + + class MatchMakingInfo final : public bdMatchmakingInfo + { + public: + int32_t playlist_number; + int32_t playlist_version; + int32_t netcode_version; + int32_t map_packs; + int32_t slots_needed_on_team; + int32_t skill; + uint32_t country_code; + uint32_t asn; + float latitude; + float longitude; + int32_t max_reserved_slots; + int32_t used_reserved_slots; + std::string game_security_key; // 16 bytes. + std::string platform_session_id; // 16 bytes. + uint32_t data_centres; + uint32_t coop_state; + + void serialize(byte_buffer* buffer) override + { + bdMatchmakingInfo::serialize(buffer); + + buffer->write_int32(this->playlist_number); + buffer->write_int32(this->playlist_version); + buffer->write_int32(this->netcode_version); + buffer->write_int32(this->map_packs); + buffer->write_int32(this->slots_needed_on_team); + buffer->write_int32(this->skill); + buffer->write_uint32(this->country_code); + buffer->write_uint32(this->asn); + buffer->write_float(this->latitude); + buffer->write_float(this->longitude); + buffer->write_int32(this->max_reserved_slots); + buffer->write_int32(this->used_reserved_slots); + buffer->write_blob(this->game_security_key); + buffer->write_blob(this->platform_session_id); + buffer->write_uint32(this->data_centres); + buffer->write_uint32(this->coop_state); + } + + void deserialize(byte_buffer* buffer) override + { + bdMatchmakingInfo::deserialize(buffer); + + buffer->read_int32(&this->playlist_number); + buffer->read_int32(&this->playlist_version); + buffer->read_int32(&this->netcode_version); + buffer->read_int32(&this->map_packs); + buffer->read_int32(&this->slots_needed_on_team); + buffer->read_int32(&this->skill); + buffer->read_uint32(&this->country_code); + buffer->read_uint32(&this->asn); + buffer->read_float(&this->latitude); + buffer->read_float(&this->longitude); + buffer->read_int32(&this->max_reserved_slots); + buffer->read_int32(&this->used_reserved_slots); + buffer->read_blob(&this->game_security_key); + buffer->read_blob(&this->platform_session_id); + buffer->read_uint32(&this->data_centres); + buffer->read_uint32(&this->coop_state); + } + }; + + class bdPerformanceValue final : public i_serializable + { + public: + uint64_t user_id; + int64_t performance; + + void serialize(byte_buffer* buffer) override + { + buffer->write_uint64(this->user_id); + buffer->write_int64(this->performance); + } + + void deserialize(byte_buffer* buffer) override + { + buffer->read_uint64(&this->user_id); + buffer->read_int64(&this->performance); + } + }; + + struct bdSockAddr final + { + bdSockAddr() : in_un(), m_family(AF_INET) + { + } + + union + { + struct + { + char m_b1; + char m_b2; + char m_b3; + char m_b4; + } m_caddr; + + unsigned int m_iaddr; + + struct + { + unsigned __int16 m_w1; + unsigned __int16 m_w2; + unsigned __int16 m_w3; + unsigned __int16 m_w4; + unsigned __int16 m_w5; + unsigned __int16 m_w6; + unsigned __int16 m_w7; + unsigned __int16 m_w8; + } m_caddr6; + + char m_iaddr6[16]; + char m_sockaddr_storage[128]; + } in_un; + + unsigned __int16 m_family; + }; + + struct bdInetAddr final : i_serializable + { + bdSockAddr m_addr; + + bool is_valid() const + { + return (this->m_addr.m_family == AF_INET /*|| this->m_addr.m_family == AF_INET6*/); + } + + void serialize(byte_buffer* buffer) override + { + const auto data_types = buffer->is_using_data_types(); + buffer->set_use_data_types(false); + + if (this->m_addr.m_family == AF_INET) + { + buffer->write(4, &this->m_addr.in_un.m_caddr); + } + + buffer->set_use_data_types(data_types); + } + + void deserialize(byte_buffer* buffer) override + { + const auto data_types = buffer->is_using_data_types(); + buffer->set_use_data_types(false); + + if (this->m_addr.m_family == AF_INET) + { + buffer->read(4, &this->m_addr.in_un.m_caddr); + } + + buffer->set_use_data_types(data_types); + } + }; + + struct bdAddr final : i_serializable + { + bdInetAddr m_address; + unsigned __int16 m_port{}; + + void serialize(byte_buffer* buffer) override + { + const bool data_types = buffer->is_using_data_types(); + buffer->set_use_data_types(false); + + this->m_address.serialize(buffer); + buffer->write_uint16(this->m_port); + + buffer->set_use_data_types(data_types); + } + + void deserialize(byte_buffer* buffer) override + { + const auto data_types = buffer->is_using_data_types(); + buffer->set_use_data_types(false); + + this->m_address.deserialize(buffer); + buffer->read_uint16(&this->m_port); + + buffer->set_use_data_types(data_types); + } + }; + + struct bdCommonAddr : i_serializable + { + bdAddr m_local_addrs[5]; + bdAddr m_public_addr; + game::native::bdNATType m_nat_type; + unsigned int m_hash; + bool m_is_loopback; + + void serialize(byte_buffer* buffer) override + { + const auto data_types = buffer->is_using_data_types(); + buffer->set_use_data_types(false); + + auto valid = true; + for (uint32_t i = 0; i < 5 && i < ARRAYSIZE(this->m_local_addrs) && valid; ++i) + { + this->m_local_addrs[i].serialize(buffer); + valid = this->m_local_addrs[i].m_address.is_valid(); + } + + if (valid) + { + this->m_public_addr.serialize(buffer); + buffer->write_byte(this->m_nat_type); + } + + buffer->set_use_data_types(data_types); + } + + void deserialize(byte_buffer* buffer) override + { + const auto data_types = buffer->is_using_data_types(); + buffer->set_use_data_types(false); + + auto valid = true; + for (uint32_t i = 0; i < ARRAYSIZE(this->m_local_addrs) && valid; ++i) + { + bdAddr addr; + addr.deserialize(buffer); + this->m_local_addrs[i] = addr; + valid = this->m_local_addrs[i].m_address.is_valid(); + } + + if (valid) + { + this->m_public_addr.deserialize(buffer); + buffer->read_byte(reinterpret_cast(&this->m_nat_type)); + } + + buffer->set_use_data_types(data_types); + } + }; +} diff --git a/src/game/demonware/i_server.hpp b/src/game/demonware/i_server.hpp new file mode 100644 index 0000000..c41813b --- /dev/null +++ b/src/game/demonware/i_server.hpp @@ -0,0 +1,182 @@ +#pragma once +#include "bit_buffer.hpp" +#include "byte_buffer.hpp" + +namespace demonware +{ + class reply + { + public: + virtual ~reply() = default; + virtual std::string get_data() = 0; + }; + + class raw_reply : public reply + { + public: + raw_reply() = default; + explicit raw_reply(std::string data) : buffer_(std::move(data)) {} + + virtual std::string get_data() override + { + return this->buffer_; + } + + protected: + std::string buffer_; + }; + + class typed_reply : public raw_reply + { + public: + typed_reply(uint8_t _type) : type_(_type) {} + + protected: + uint8_t get_type() const { return this->type_; } + + private: + uint8_t type_; + }; + + class encrypted_reply final : public typed_reply + { + public: + encrypted_reply(const uint8_t type, bit_buffer* bbuffer) : typed_reply(type) + { + this->buffer_.append(bbuffer->get_buffer()); + } + encrypted_reply(const uint8_t type, byte_buffer* bbuffer) : typed_reply(type) + { + this->buffer_.append(bbuffer->get_buffer()); + } + + virtual std::string get_data() override; + }; + + class unencrypted_reply final : public typed_reply + { + public: + unencrypted_reply(uint8_t _type, bit_buffer* bbuffer) : typed_reply(_type) + { + this->buffer_.append(bbuffer->get_buffer()); + } + unencrypted_reply(uint8_t _type, byte_buffer* bbuffer) : typed_reply(_type) + { + this->buffer_.append(bbuffer->get_buffer()); + } + + virtual std::string get_data() override; + }; + + class remote_reply; + class service_reply; + + class i_server + { + public: + virtual ~i_server() = default; + virtual int send(const char* buf, int len) = 0; + virtual int recv(char* buf, int len) = 0; + + virtual void send_reply(reply* reply) = 0; + + virtual std::shared_ptr create_message(uint8_t type) + { + auto reply = std::make_shared(this, type); + return reply; + } + + virtual std::shared_ptr create_reply(uint8_t type, uint32_t error = 0 /*Game::bdLobbyErrorCode::BD_NO_ERROR*/) + { + auto reply = std::make_shared(this, type, error); + return reply; + } + }; + + class remote_reply final + { + public: + remote_reply(i_server* server, uint8_t _type) : type_(_type), server_(server) {} + + template + void send(BufferType* buffer, const bool encrypted) + { + std::unique_ptr reply; + + if (encrypted) reply = std::make_unique(this->type_, buffer); + else reply = std::make_unique(this->type_, buffer); + this->server_->send_reply(reply.get()); + } + + uint8_t get_type() const { return this->type_; } + + private: + uint8_t type_; + i_server* server_; + }; + + class i_serializable + { + public: + virtual ~i_serializable() = default; + virtual void serialize(byte_buffer* /*buffer*/) {} + virtual void deserialize(byte_buffer* /*buffer*/) {} + }; + + class service_reply final + { + public: + service_reply(i_server* _server, uint8_t _type, uint32_t _error) : type_(_type), error_(_error), reply_(_server, 1) {} + + uint64_t send() + { + static uint64_t id = 0x8000000000000001; + const auto transaction_id = ++id; + + byte_buffer buffer; + buffer.write_uint64(transaction_id); + buffer.write_uint32(this->error_); + buffer.write_byte(this->type_); + + if (!this->error_) + { + buffer.write_uint32(uint32_t(this->objects_.size())); + if (!this->objects_.empty()) + { + buffer.write_uint32(uint32_t(this->objects_.size())); + + for (auto& object : this->objects_) + { + object->serialize(&buffer); + } + + this->objects_.clear(); + } + } + else + { + buffer.write_uint64(transaction_id); + } + + this->reply_.send(&buffer, true); + return transaction_id; + } + + void add(const std::shared_ptr& object) + { + this->objects_.push_back(object); + } + + void add(i_serializable* object) + { + this->add(std::shared_ptr(object)); + } + + private: + uint8_t type_; + uint32_t error_; + remote_reply reply_; + + std::vector> objects_; + }; +} diff --git a/src/game/demonware/i_service.hpp b/src/game/demonware/i_service.hpp new file mode 100644 index 0000000..6784cd3 --- /dev/null +++ b/src/game/demonware/i_service.hpp @@ -0,0 +1,70 @@ +#pragma once +#include "i_server.hpp" + +namespace demonware +{ + class i_service + { + public: + virtual ~i_service() = default; + i_service() = default; + + // Copying or moving a service object won't work + // as the callbacks are bound to the initial object pointer + // Therefore, you should never declare copy/move + // constructors when inheriting from IService! + i_service(i_service&&) = delete; + i_service(const i_service&) = delete; + i_service& operator=(const i_service&) = delete; + + typedef std::function Callback; + + virtual uint16_t getType() = 0; + + virtual void call_service(i_server* server, const std::string& data) + { + std::lock_guard _(this->mutex_); + + byte_buffer buffer(data); + buffer.read_byte(&this->sub_type_); + + printf("DW: Handling subservice of type %d\n", this->sub_type_); + + const auto callback = this->callbacks.find(this->sub_type_); + if (callback != this->callbacks.end()) + { + callback->second(server, &buffer); + } + else + { + printf("DW: Missing subservice %d for type %d\n", this->sub_type_, this->getType()); + } + } + + protected: + std::map callbacks{}; + + template + void register_service(const uint8_t type, T(Class::*callback)(Args ...)) + { + this->callbacks[type] = [this, callback](Args... args) -> T + { + return (reinterpret_cast(this)->*callback)(args...); + }; + } + + uint8_t get_sub_type() const { return this->sub_type_; } + + private: + std::mutex mutex_; + + uint8_t sub_type_{}; + }; + + template + class i_generic_service final : 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 new file mode 100644 index 0000000..a42f361 --- /dev/null +++ b/src/game/demonware/service_server.cpp @@ -0,0 +1,196 @@ +#include +#include "service_server.hpp" +#include "utils/cryptography.hpp" + +namespace demonware +{ + std::string unencrypted_reply::get_data() + { + byte_buffer result; + result.set_use_data_types(false); + + result.write_int32(static_cast(this->buffer_.size()) + 2); + result.write_bool(false); + result.write_byte(this->get_type()); + result.write(this->buffer_); + + return result.get_buffer(); + } + + std::string encrypted_reply::get_data() + { + byte_buffer result; + result.set_use_data_types(false); + + byte_buffer enc_buffer; + enc_buffer.set_use_data_types(false); + enc_buffer.write_int32(0xDEADBEEF); + enc_buffer.write_byte(this->get_type()); + enc_buffer.write(this->buffer_); + + auto data = enc_buffer.get_buffer(); + + auto size = enc_buffer.size(); + size = ~7 & (size + 7); // 8 byte align + data.resize(size); + + result.write_int32(static_cast(size) + 5); + result.write_byte(true); + + auto seed = 0x13371337; + 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))); + + return result.get_buffer(); + } + + service_server::service_server(std::string _name) : name_(std::move(_name)) + { + this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_); + } + + unsigned long service_server::get_address() const + { + return this->address_; + } + + int service_server::send(const char* buf, const int len) + { + if (len <= 3) return -1; + std::lock_guard _(this->mutex_); + + this->incoming_queue_.push(std::string(buf, len)); + //this->parsePacket(Utils::String(buf, len)); + + return len; + } + + int service_server::recv(char* buf, int len) + { + if (len > 0 && !this->outgoing_queue_.empty()) + { + std::lock_guard _(this->mutex_); + + len = std::min(len, static_cast(this->outgoing_queue_.size())); + for (auto i = 0; i < len; ++i) + { + buf[i] = this->outgoing_queue_.front(); + this->outgoing_queue_.pop(); + } + + return len; + } + + return SOCKET_ERROR; + } + + void service_server::send_reply(reply* data) + { + if (!data) return; + + std::lock_guard _(this->mutex_); + + this->reply_sent_ = true; + const auto buffer = data->get_data(); + for (auto& byte : buffer) + { + this->outgoing_queue_.push(byte); + } + } + + void service_server::call_handler(uint8_t type, const std::string& data) + { + if (this->services_.find(type) != this->services_.end()) + { + this->services_[type]->call_service(this, data); + } + else + { + printf("DW: Missing handler of type %d\n", type); + } + } + + void service_server::run_frame() + { + if (!this->incoming_queue_.empty()) + { + std::lock_guard _(this->mutex_); + const std::string packet = this->incoming_queue_.front(); + this->incoming_queue_.pop(); + + this->parse_packet(packet); + } + } + + void service_server::parse_packet(const std::string& packet) + { + byte_buffer buffer(packet); + buffer.set_use_data_types(false); + + try + { + while (!packet.empty()) + { + int size; + buffer.read_int32(&size); + + if (size <= 0) + { + const std::string zero("\x00\x00\x00\x00", 4); + + raw_reply reply(zero); + this->send_reply(&reply); + return; + } + else if (size == 200) // Connection id + { + byte_buffer bbufer; + bbufer.write_uint64(0x00000000000000FD); + + auto reply = this->create_message(4); + reply->send(&bbufer, false); + return; + } + + if (buffer.size() < size_t(size)) return; + + byte_buffer p_buffer; + p_buffer.set_use_data_types(false); + p_buffer.get_buffer().resize(size); + buffer.read(size, p_buffer.get_buffer().data()); + + bool enc; + p_buffer.read_bool(&enc); + + if (enc) + { + int iv; + 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)); + + int checksum; + p_buffer.read_int32(&checksum); + } + + uint8_t type; + p_buffer.read_byte(&type); + printf("DW: Handling message of type %d (encrypted: %d)\n", type, enc); + + this->reply_sent_ = false; + this->call_handler(type, p_buffer.get_buffer()); + + if (!this->reply_sent_ && type != 7) + { + this->create_reply(type)->send(); + } + } + } + catch (...) + { + } + } +} diff --git a/src/game/demonware/service_server.hpp b/src/game/demonware/service_server.hpp new file mode 100644 index 0000000..0c894e0 --- /dev/null +++ b/src/game/demonware/service_server.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "i_service.hpp" + +namespace demonware +{ + class service_server final : public i_server + { + public: + explicit service_server(std::string name); + + template void register_service() + { + static_assert(std::is_base_of::value, "Service must inherit from IService"); + + auto service = std::make_unique(); + const uint16_t type = service->getType(); + + this->services_[type] = std::move(service); + } + + unsigned long get_address() const; + + int send(const char* buf, int len) override; + int recv(char* buf, int len) override; + void send_reply(reply* data) override; + + void call_handler(uint8_t type, const std::string& data); + void run_frame(); + + private: + std::string name_; + + std::recursive_mutex mutex_; + std::queue outgoing_queue_; + std::queue incoming_queue_; + std::map> services_; + unsigned long address_ = 0; + bool reply_sent_ = false; + + void parse_packet(const std::string& packet); + }; +} diff --git a/src/game/demonware/stun_server.cpp b/src/game/demonware/stun_server.cpp new file mode 100644 index 0000000..dc5d2a7 --- /dev/null +++ b/src/game/demonware/stun_server.cpp @@ -0,0 +1,70 @@ +#include +#include +#include "stun_server.hpp" +#include "utils/cryptography.hpp" +#include "byte_buffer.hpp" + +namespace demonware +{ + stun_server::stun_server(std::string _name) : name_(std::move(_name)) + { + this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_); + } + + unsigned long stun_server::get_address() const + { + return this->address_; + } + + void stun_server::ip_discovery(SOCKET s, const sockaddr* to, int tolen) const + { + const uint32_t ip = 0x0100007f; + + byte_buffer buffer; + buffer.set_use_data_types(false); + buffer.write_byte(31); // type + buffer.write_byte(2); // version + buffer.write_byte(0); // version + buffer.write_uint32(ip); // external ip + buffer.write_uint16(3074); // port + + //Components::DemonWare::SendDatagramPacket(s, buffer, to, tolen); + } + + void stun_server::nat_discovery(SOCKET s, const sockaddr* to, int tolen) const + { + const uint32_t ip = 0x0100007f; + + byte_buffer buffer; + buffer.set_use_data_types(false); + buffer.write_byte(21); // type + buffer.write_byte(2); // version + buffer.write_byte(0); // version + buffer.write_uint32(ip); // external ip + buffer.write_uint16(3074); // port + buffer.write_uint32(this->get_address()); // server ip + buffer.write_uint16(3074); // server port + + //Components::DemonWare::SendDatagramPacket(s, buffer, to, tolen); + } + + int stun_server::send(const SOCKET s, const char* buf, int len, const sockaddr* to, int tolen) const + { + uint8_t type, version, padding; + + byte_buffer buffer(std::string(buf, len)); + buffer.set_use_data_types(false); + buffer.read_byte(&type); + buffer.read_byte(&version); + buffer.read_byte(&padding); + + switch (type) + { + case 30: this->ip_discovery(s, to, tolen); break; + case 20: this->nat_discovery(s, to, tolen); break; + default: break; + } + + return len; + } +} diff --git a/src/game/demonware/stun_server.hpp b/src/game/demonware/stun_server.hpp new file mode 100644 index 0000000..9a9f0fe --- /dev/null +++ b/src/game/demonware/stun_server.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace demonware +{ + class stun_server final + { + public: + explicit stun_server(std::string name); + + unsigned long get_address() const; + + int send(SOCKET s, const char* buf, int len, const sockaddr* to, int tolen) const; + + private: + std::string name_; + unsigned long address_; + + void ip_discovery(SOCKET s, const sockaddr* to, int tolen) const; + void nat_discovery(SOCKET s, const sockaddr* to, int tolen) const; + }; +} diff --git a/src/game/game.cpp b/src/game/game.cpp index a050c7f..09230c3 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -5,6 +5,8 @@ namespace game { namespace native { + Conbuf_AppendText_t Conbuf_AppendText; + Sys_ShowConsole_t Sys_ShowConsole; } @@ -30,6 +32,8 @@ namespace game { mode = _mode; + native::Conbuf_AppendText = native::Conbuf_AppendText_t(SELECT_VALUE(0x4C84E0, 0x5CF610, 0x53C790)); + native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590, 0)); } } diff --git a/src/game/game.hpp b/src/game/game.hpp index 15df333..a940900 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -1,5 +1,6 @@ #pragma once +#include "structs.hpp" #include "launcher/launcher.hpp" #define SELECT_VALUE(sp, mp, dedi) (game::is_sp() ? (sp) : (game::is_mp() ? (mp) : (dedi))) @@ -8,13 +9,16 @@ namespace game { namespace native { - typedef void(*Sys_ShowConsole_t)(); + typedef void (*Conbuf_AppendText_t)(const char* message); + extern Conbuf_AppendText_t Conbuf_AppendText; + + typedef void (*Sys_ShowConsole_t)(); extern Sys_ShowConsole_t Sys_ShowConsole; } - + bool is_mp(); bool is_sp(); bool is_dedi(); void initialize(launcher::mode mode); -} \ No newline at end of file +} diff --git a/src/game/structs.hpp b/src/game/structs.hpp new file mode 100644 index 0000000..c9be37f --- /dev/null +++ b/src/game/structs.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace game +{ + namespace native + { + enum bdNATType : uint8_t + { + BD_NAT_UNKNOWN = 0x0, + BD_NAT_OPEN = 0x1, + BD_NAT_MODERATE = 0x2, + BD_NAT_STRICT = 0x3, + }; + } +} diff --git a/src/launcher/image.cpp b/src/launcher/image.cpp index 74ebe8d..afcf3c3 100644 --- a/src/launcher/image.cpp +++ b/src/launcher/image.cpp @@ -59,13 +59,13 @@ void image::set_click_listener(std::function callback) void image::click(const POINT& mouse, const bool down) { - if(down) + if (down) { this->down_handled_ = this->is_hovered(mouse); } else { - if(this->down_handled_ && this->is_hovered(mouse) && this->callback_) + if (this->down_handled_ && this->is_hovered(mouse) && this->callback_) { this->callback_(); } diff --git a/src/launcher/launcher.cpp b/src/launcher/launcher.cpp index 312e0b4..ea66228 100644 --- a/src/launcher/launcher.cpp +++ b/src/launcher/launcher.cpp @@ -71,7 +71,7 @@ void launcher::draw_text(const HDC hdc) Gdiplus::RectF rect{}; graphics.MeasureString(sp.data(), -1, &font, rect, stringformat, &rect); - Gdiplus::PointF pos{150 - (rect.Width / 2 + 2), 45}; + const Gdiplus::PointF pos{150 - (rect.Width / 2 + 2), 45}; graphics.DrawString(sp.data(), -1, &font, pos, &color); rect = {}; diff --git a/src/loader/loader.cpp b/src/loader/loader.cpp index c89a76b..adeec3a 100644 --- a/src/loader/loader.cpp +++ b/src/loader/loader.cpp @@ -94,7 +94,7 @@ void loader::load_sections(const utils::nt::module& target, const utils::nt::mod void loader::load_imports(const utils::nt::module& target, const utils::nt::module& source) const { - IMAGE_DATA_DIRECTORY* import_directory = &source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + const auto import_directory = &source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; auto descriptor = PIMAGE_IMPORT_DESCRIPTOR(target.get_ptr() + import_directory->VirtualAddress); diff --git a/src/loader/module.hpp b/src/loader/module.hpp index b3c9a8c..10938f5 100644 --- a/src/loader/module.hpp +++ b/src/loader/module.hpp @@ -7,6 +7,10 @@ public: { } + virtual void post_start() + { + } + virtual void post_load() { } diff --git a/src/loader/module_loader.cpp b/src/loader/module_loader.cpp index 63cd21f..1d4b2bc 100644 --- a/src/loader/module_loader.cpp +++ b/src/loader/module_loader.cpp @@ -14,6 +14,18 @@ void module_loader::register_module(std::unique_ptr&& module_) modules_->push_back(std::move(module_)); } +void module_loader::post_start() +{ + static auto handled = false; + if (handled || !modules_) return; + handled = true; + + for (const auto& module_ : *modules_) + { + module_->post_start(); + } +} + void module_loader::post_load() { static auto handled = false; diff --git a/src/loader/module_loader.hpp b/src/loader/module_loader.hpp index 8a877b3..1ee9999 100644 --- a/src/loader/module_loader.hpp +++ b/src/loader/module_loader.hpp @@ -18,6 +18,7 @@ public: static void register_module(std::unique_ptr&& module); + static void post_start(); static void post_load(); static void pre_destroy(); diff --git a/src/main.cpp b/src/main.cpp index 1b964ed..e09c534 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,7 @@ void exit_hook(const int code) exit(code); } -int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/) +int main() { FARPROC entry_point = nullptr; @@ -24,6 +24,8 @@ int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR return 0; #endif + module_loader::post_start(); + launcher launcher; const auto mode = launcher.run(); @@ -50,7 +52,7 @@ int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR game::initialize(mode); module_loader::post_load(); } - catch (std::exception e) + catch (std::exception& e) { MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR); return 1; diff --git a/src/module/console.cpp b/src/module/console.cpp index 99d53a7..cd272ce 100644 --- a/src/module/console.cpp +++ b/src/module/console.cpp @@ -1,16 +1,105 @@ #include #include "loader/module_loader.hpp" -#include "console.hpp" #include "game/game.hpp" class console final : public module { public: + console() + { + ShowWindow(GetConsoleWindow(), SW_HIDE); + + _pipe(this->handles_, 1024, _O_TEXT); + _dup2(this->handles_[1], 1); + _dup2(this->handles_[1], 2); + + setvbuf(stdout, nullptr, _IONBF, 0); + setvbuf(stderr, nullptr, _IONBF, 0); + } + + void post_start() override + { + this->console_runner_ = std::thread(std::bind(&console::runner, this)); + } + + void pre_destroy() override + { + this->terminate_runner_ = true; + + printf("\r\n"); + _flushall(); + + _close(this->handles_[0]); + _close(this->handles_[1]); + + if (this->console_runner_.joinable()) + { + this->console_runner_.join(); + } + } + void post_load() override { if (game::is_dedi()) return; game::native::Sys_ShowConsole(); + + std::lock_guard _(this->mutex_); + this->console_initialized_ = true; + this->replay_messages(); + } + +private: + bool console_initialized_ = false; + bool terminate_runner_ = false; + + std::mutex mutex_; + std::thread console_runner_; + std::queue message_queue_; + + int handles_[2]{}; + + void replay_messages() + { + if (!this->console_initialized_) return; + + while (!this->message_queue_.empty()) + { + this->push_message(this->message_queue_.front()); + this->message_queue_.pop(); + } + } + + void push_message(const std::string& message) + { + if (!this->console_initialized_) + { + std::lock_guard _(this->mutex_); + this->message_queue_.push(message); + return; + } + + game::native::Conbuf_AppendText(message.data()); + } + + void runner() + { + char buffer[1024]; + + while (!this->terminate_runner_ && this->handles_[0]) + { + const auto len = _read(this->handles_[0], buffer, sizeof(buffer)); + if (len > 0) + { + this->push_message(std::string(buffer, len)); + } + else + { + std::this_thread::sleep_for(10ms); + } + } + + std::this_thread::yield(); } }; diff --git a/src/module/console.hpp b/src/module/console.hpp deleted file mode 100644 index 6f70f09..0000000 --- a/src/module/console.hpp +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/src/module/dw.cpp b/src/module/dw.cpp index 7ce88d9..9412ce6 100644 --- a/src/module/dw.cpp +++ b/src/module/dw.cpp @@ -1,5 +1,7 @@ #include #include "loader/module_loader.hpp" +#include "utils/hook.hpp" +#include "game/game.hpp" class dw final : public module { @@ -8,6 +10,26 @@ public: { // TODO Patch DW } + + void post_load() override + { + 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, ...) + { + char buffer[2048]; + + va_list ap; + va_start(ap, msg); + + vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap); + printf("%s: %s\n", function, buffer); + + va_end(ap); + } }; REGISTER_MODULE(dw) diff --git a/src/std_include.hpp b/src/std_include.hpp index b1561a6..4475d2a 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include // min and max is required by gdi, therefore NOMINMAX won't work #ifdef max @@ -24,8 +27,10 @@ #include #include #include +#include #include #include +#include #include #include diff --git a/src/utils/cryptography.cpp b/src/utils/cryptography.cpp index 55c0626..a788767 100644 --- a/src/utils/cryptography.cpp +++ b/src/utils/cryptography.cpp @@ -259,7 +259,7 @@ namespace utils return compute(data.data(), data.size()); } - unsigned int jenkins_one_at_a_time::compute(const char* key, size_t len) + unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len) { unsigned int hash, i; for (hash = i = 0; i < len; ++i)