Squashed commit of the following:

commit f44d146e4ac906ff898e8968c6857fd2280f6d20
Author: project-bo4 <127137346+project-bo4@users.noreply.github.com>
Date:   Thu May 11 13:39:29 2023 -0700

    remove lpc package from repository

commit 29fb0f63e50de468ab0b9db35f7e8fdadf58afc3
Author: project-bo4 <themanwithantidote@gmail.com>
Date:   Thu May 11 13:06:13 2023 -0700

    generic improvements

    + added identity configuration
    + objectstore improvements
    + added battlenet 'US' servers to blocklist
    + some other minor improvements

commit 5d5e31099ebcce5a637b9a02d4936a97fb0802a7
Author: project-bo4 <themanwithantidote@gmail.com>
Date:   Sat Mar 25 16:32:52 2023 -0700

    lpc improvements

    + fix lpc issue with foreign languages(non-english) not being considered in listing
    + check lpc files pre-start and warn user if missing any of core items

commit 2de1a54b6f4cc5c44dc906871021cfbc85057ca9
Author: project-bo4 <themanwithantidote@gmail.com>
Date:   Thu Mar 23 12:45:56 2023 -0700

    demonware improvements

    + handle object store uploadUserObject
    + silence bdUNK125

commit 710dcc3ec6178071d67c27e5bf6705f08cabe7e2
Author: project-bo4 <themanwithantidote@gmail.com>
Date:   Mon Mar 20 13:43:56 2023 -0700

    forgot to update names

commit 217682dfb07b35f7a09399fb2698924bb7fe8b77
Author: project-bo4 <themanwithantidote@gmail.com>
Date:   Mon Mar 20 11:09:18 2023 -0700

    upload lpc and update readme

commit 83f812b33b90791a8d13b9468f27da51e0a4f2b9
Author: project-bo4 <themanwithantidote@gmail.com>
Date:   Mon Mar 20 10:53:43 2023 -0700

    demonware emulator

    + demonware emulator
    + platform name and id
    + xxhash and picoproto
    - remove g_runframe hook
    -disable discovery(save time)
    + improvements to utils
    + add new resources
This commit is contained in:
project-bo4
2023-05-11 13:50:11 -07:00
parent 7e2d784c22
commit 354d022967
98 changed files with 5460 additions and 87 deletions

View File

@ -0,0 +1,185 @@
#include <std_include.hpp>
#include "auth3_server.hpp"
#include "../keys.hpp"
#include <utils/cryptography.hpp>
#include <utils/string.hpp>
namespace demonware
{
namespace
{
#pragma pack(push, 1)
struct auth_ticket
{
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)
}
void auth3_server::send_reply(reply* data)
{
if (!data) return;
this->send(data->data());
}
void auth3_server::handle(const std::string& packet)
{
if (packet.starts_with("POST /auth/"))
{
#ifndef NDEBUG
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [auth]: user requested authentication.");
#endif
return;
}
unsigned int title_id = 0;
unsigned int iv_seed = 0;
std::string identity{};
std::string session_token{};
std::string account_token{};
rapidjson::Document j;
j.Parse(packet.data(), packet.size());
if (j.HasMember("title_id") && j["title_id"].IsString())
{
title_id = std::stoul(j["title_id"].GetString());
}
if (j.HasMember("iv_seed") && j["iv_seed"].IsString())
{
iv_seed = std::stoul(j["iv_seed"].GetString());
}
if (j.HasMember("identity") && j["identity"].IsString())
{
identity = j["identity"].GetString();
}
if (j.HasMember("extra_data") && j["extra_data"].IsString())
{
rapidjson::Document extra_data;
auto& ed = j["extra_data"];
extra_data.Parse(ed.GetString(), ed.GetStringLength());
if (extra_data.HasMember("session_token") && extra_data["session_token"].IsString())
{
auto& token_field = extra_data["session_token"];
std::string token_b64(token_field.GetString(), token_field.GetStringLength());
session_token = token_b64;
}
if (extra_data.HasMember("account_token") && extra_data["account_token"].IsString())
{
auto& token_field = extra_data["account_token"];
std::string token_b64(token_field.GetString(), token_field.GetStringLength());
account_token = utils::cryptography::base64::decode(token_b64);
}
}
std::string session_key(
"\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37", 24);
// client_ticket
auth_ticket ticket{};
std::memset(&ticket, 0x0, sizeof ticket);
ticket.m_magicNumber = 0xEFBDADDE;
ticket.m_type = 0;
ticket.m_titleID = title_id;
ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
ticket.m_timeExpires = ticket.m_timeIssued + 30000;
ticket.m_licenseID = 0;
ticket.m_userID = 0;
strncpy_s(ticket.m_username, sizeof(ticket.m_username), session_token.data(), session_token.length());
std::memcpy(ticket.m_sessionKey, session_key.data(), 24);
const auto client_ticket_b64 = utils::cryptography::base64::encode(reinterpret_cast<const unsigned char*>(&ticket), sizeof(ticket));
// server_ticket
uint8_t server_ticket[128];
std::memset(&server_ticket, 0, sizeof server_ticket);
std::memcpy(server_ticket, session_key.data(), 24);
const auto server_ticket_b64 = utils::cryptography::base64::encode(server_ticket, 128);
demonware::set_session_key(session_key);
// header time
char date[64];
const auto now = time(nullptr);
tm gmtm{};
gmtime_s(&gmtm, &now);
strftime(date, 64, "%a, %d %b %G %T", &gmtm);
rapidjson::Document extra;
extra.SetObject();
std::string username = std::string(ticket.m_username, sizeof(ticket.m_username)).data();
extra.AddMember("username", username, extra.GetAllocator());
extra.AddMember("time_to_live", 9999, extra.GetAllocator());
const auto lul = utils::cryptography::base64::encode("lul");
extra.AddMember("extended_data", lul, extra.GetAllocator());
rapidjson::StringBuffer extra_buffer{};
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
extra_writer(extra_buffer);
extra.Accept(extra_writer);
std::string extra_data{};
extra_data.append(extra_buffer.GetString(), extra_buffer.GetLength());
// json content
rapidjson::Document doc;
doc.SetObject();
doc.AddMember("auth_task", "85", doc.GetAllocator());
doc.AddMember("code", "700", doc.GetAllocator());
auto seed = std::to_string(iv_seed);
doc.AddMember("iv_seed", seed, doc.GetAllocator());
doc.AddMember("client_ticket", client_ticket_b64, doc.GetAllocator());
doc.AddMember("server_ticket", server_ticket_b64, doc.GetAllocator());
doc.AddMember("client_id", "treyarch-cod-t8-bnet", doc.GetAllocator());
doc.AddMember("account_type", "bnet", doc.GetAllocator());
doc.AddMember("crossplay_enabled", false, doc.GetAllocator());
doc.AddMember("loginqueue_eanbled", false, doc.GetAllocator());
doc.AddMember("identity", identity, doc.GetAllocator());
doc.AddMember("extra_data", extra_data, doc.GetAllocator());
doc.AddMember("service_level", "paid", doc.GetAllocator());
rapidjson::Value value{};
doc.AddMember("lsg_endpoint", value, doc.GetAllocator());
rapidjson::StringBuffer buffer{};
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
writer(buffer);
doc.Accept(writer);
// http stuff
std::string result;
result.append("HTTP/1.1 200 OK\r\n");
result.append("Server: TornadoServer/4.5.3\r\n");
result.append("Content-Type: application/json\r\n");
result.append(utils::string::va("Date: %s GMT\r\n", date));
result.append(utils::string::va("Content-Length: %d\r\n\r\n", buffer.GetLength()));
result.append(buffer.GetString(), buffer.GetLength());
raw_reply reply(result);
this->send_reply(&reply);
#ifndef NDEBUG
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [auth]: user successfully authenticated.");
#endif
}
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "tcp_server.hpp"
#include "../reply.hpp"
namespace demonware
{
class auth3_server : public tcp_server
{
public:
using tcp_server::tcp_server;
private:
void send_reply(reply* data);
void handle(const std::string& packet) override;
};
}

View File

@ -0,0 +1,22 @@
#include <std_include.hpp>
#include "base_server.hpp"
#include <utils/cryptography.hpp>
namespace demonware
{
base_server::base_server(std::string name): name_(std::move(name))
{
this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_);
}
const std::string& base_server::get_name() const
{
return this->name_;
}
uint32_t base_server::get_address() const
{
return this->address_;
}
}

View File

@ -0,0 +1,30 @@
#pragma once
namespace demonware
{
class base_server
{
public:
using stream_queue = std::queue<char>;
using data_queue = std::queue<std::string>;
base_server(std::string name);
base_server(base_server&&) = delete;
base_server(const base_server&) = delete;
base_server& operator=(base_server&&) = delete;
base_server& operator=(const base_server&) = delete;
virtual ~base_server() = default;
const std::string& get_name() const;
uint32_t get_address() const;
virtual void frame() = 0;
private:
std::string name_;
std::uint32_t address_ = 0;
};
}

View File

@ -0,0 +1,181 @@
#include <std_include.hpp>
#include "lobby_server.hpp"
#include "../services.hpp"
#include "../keys.hpp"
#include <utils/cryptography.hpp>
namespace demonware
{
lobby_server::lobby_server(std::string name) : tcp_server(std::move(name))
{
this->register_service<bdStats>();
this->register_service<bdProfiles>();
this->register_service<bdTitleUtilities>();
this->register_service<bdKeyArchive>();
this->register_service<bdBandwidthTest>();
this->register_service<bdCounter>();
this->register_service<bdDML>();
this->register_service<bdGroup>();
this->register_service<bdAnticheat>();
this->register_service<bdTags>();
this->register_service<bdPooledStorage>();
this->register_service<bdEventLog>();
this->register_service<bdRichPresence>();
this->register_service<bdMarketplace>();
this->register_service<bdPublisherVariables>();
this->register_service<bdMarketingComms>();
this->register_service<bdUNK125>();
this->register_service<bdObjectStore>();
this->register_service<bdLootGeneration>();
};
void lobby_server::send_reply(reply* data)
{
if (!data) return;
this->send(data->data());
}
void lobby_server::handle(const std::string& packet)
{
byte_buffer buffer(packet);
buffer.set_use_data_types(false);
try
{
while (buffer.has_more_data())
{
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 == 0xC8)
{
#ifndef NDEBUG
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [lobby]: received client_header_ack.");
#endif
int c8;
buffer.read_int32(&c8);
std::string packet_1 = buffer.get_remaining();
demonware::queue_packet_to_hash(packet_1);
/* msgType[BYTE] serverSelectedProto[DWORD] cypher210ConnID[QWORD] serverNonce[QWORD] */
/* 0x81(129) 0xD2(210) 0x3713371337133713 0x3713371337133713 */
const std::string packet_2(
"\x16\x00\x00\x00\xab\x81\xd2\x00\x00\x00\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37",
26);
demonware::queue_packet_to_hash(packet_2);
raw_reply reply(packet_2);
this->send_reply(&reply);
#ifndef NDEBUG
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [lobby]: sending server_header_ack.");
#endif
return;
}
if (buffer.size() < size_t(size)) return;
uint8_t check_ab;
buffer.read_ubyte(&check_ab);
if (check_ab == 0xAB)
{
uint8_t type;
buffer.read_ubyte(&type);
if (type == 0x82)
{
#ifndef NDEBUG
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [lobby]: received client_auth.");
#endif
std::string packet_3(packet.data(), packet.size() - 8); // this 8 are client hash check?
demonware::queue_packet_to_hash(packet_3);
demonware::derive_keys_iw8();
char buff[14] = "\x0A\x00\x00\x00\xAB\x83";
std::memcpy(&buff[6], demonware::get_response_id().data(), 8);
std::string response(buff, 14);
raw_reply reply(response);
this->send_reply(&reply);
#ifndef NDEBUG
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [lobby]: sending server_auth_done.");
#endif
return;
}
else if (type == 0x85)
{
uint32_t msg_count;
buffer.read_uint32(&msg_count);
char seed[16];
buffer.read(16, &seed);
std::string enc = buffer.get_remaining();
char hash[8];
std::memcpy(hash, &(enc.data()[enc.size() - 8]), 8);
std::string dec = utils::cryptography::aes::decrypt(
std::string(enc.data(), enc.size() - 8), std::string(seed, 16),
demonware::get_decrypt_key());
byte_buffer serv(dec);
serv.set_use_data_types(false);
uint32_t serv_size;
serv.read_uint32(&serv_size);
uint8_t magic; // 0x86
serv.read_ubyte(&magic);
uint8_t service_id;
serv.read_ubyte(&service_id);
this->call_service(service_id, serv.get_remaining());
return;
}
}
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [lobby]: ERROR! received unk message.");
return;
}
}
catch (...)
{
}
}
void lobby_server::call_service(const uint8_t id, const std::string& data)
{
const auto& it = this->services_.find(id);
if (it != this->services_.end())
{
it->second->exec_task(this, data);
}
else
{
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [lobby]: missing service '%s'", utils::string::va("%d", id));
// return no error
byte_buffer buffer(data);
uint8_t task_id;
buffer.read_ubyte(&task_id);
this->create_reply(task_id)->send();
}
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "tcp_server.hpp"
#include "service_server.hpp"
#include "../service.hpp"
namespace demonware
{
class lobby_server : public tcp_server, service_server
{
public:
lobby_server(std::string name);
template <typename T>
void register_service()
{
static_assert(std::is_base_of<service, T>::value, "service must inherit from service");
auto service = std::make_unique<T>();
const uint8_t id = service->id();
this->services_[id] = std::move(service);
}
void send_reply(reply* data) override;
private:
std::unordered_map<uint8_t, std::unique_ptr<service>> services_;
void handle(const std::string& packet) override;
void call_service(uint8_t id, const std::string& data);
};
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "../reply.hpp"
namespace demonware
{
class service_server
{
public:
virtual ~service_server() = default;
virtual std::shared_ptr<remote_reply> create_message(uint8_t type)
{
auto reply = std::make_shared<remote_reply>(this, type);
return reply;
}
virtual std::shared_ptr<service_reply> create_reply(uint8_t type, uint32_t error = 0)
{
auto reply = std::make_shared<service_reply>(this, type, error, false);
return reply;
}
virtual std::shared_ptr<structure_reply> create_structed_reply(uint8_t type)
{
auto reply = std::make_shared<structure_reply>(this, type);
return reply;
}
virtual void send_reply(reply* data) = 0;
};
}

View File

@ -0,0 +1,62 @@
#include <std_include.hpp>
#include "stun_server.hpp"
#include "../byte_buffer.hpp"
namespace demonware
{
void stun_server::handle(const endpoint_data& endpoint, const std::string& packet)
{
uint8_t type, version, padding;
byte_buffer buffer(packet);
buffer.set_use_data_types(false);
buffer.read_ubyte(&type);
buffer.read_ubyte(&version);
buffer.read_ubyte(&padding);
switch (type)
{
case 30:
this->ip_discovery(endpoint);
break;
case 20:
this->nat_discovery(endpoint);
break;
default:
break;
}
}
void stun_server::ip_discovery(const endpoint_data& endpoint)
{
const uint32_t ip = 0x0100007f;
byte_buffer buffer;
buffer.set_use_data_types(false);
buffer.write_ubyte(31); // type
buffer.write_ubyte(2); // version
buffer.write_ubyte(0); // version
buffer.write_uint32(ip); // external ip
buffer.write_uint16(3074); // port
this->send(endpoint, buffer.get_buffer());
}
void stun_server::nat_discovery(const endpoint_data& endpoint)
{
const uint32_t ip = 0x0100007f;
byte_buffer buffer;
buffer.set_use_data_types(false);
buffer.write_ubyte(21); // type
buffer.write_ubyte(2); // version
buffer.write_ubyte(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
this->send(endpoint, buffer.get_buffer());
}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include "udp_server.hpp"
namespace demonware
{
class stun_server : public udp_server
{
public:
using udp_server::udp_server;
private:
void handle(const endpoint_data& endpoint, const std::string& packet) override;
void ip_discovery(const endpoint_data& endpoint);
void nat_discovery(const endpoint_data& endpoint);
};
}

View File

@ -0,0 +1,84 @@
#include <std_include.hpp>
#include "tcp_server.hpp"
namespace demonware
{
void tcp_server::handle_input(const char* buf, size_t size)
{
in_queue_.access([&](data_queue& queue)
{
queue.emplace(buf, size);
});
}
size_t tcp_server::handle_output(char* buf, size_t size)
{
if (out_queue_.get_raw().empty())
{
return 0;
}
return out_queue_.access<size_t>([&](stream_queue& queue)
{
for (size_t i = 0; i < size; ++i)
{
if (queue.empty())
{
return i;
}
buf[i] = queue.front();
queue.pop();
}
return size;
});
}
bool tcp_server::pending_data()
{
return !this->out_queue_.get_raw().empty();
}
void tcp_server::frame()
{
if (this->in_queue_.get_raw().empty())
{
return;
}
while (true)
{
std::string packet{};
const auto result = this->in_queue_.access<bool>([&](data_queue& queue)
{
if (queue.empty())
{
return false;
}
packet = std::move(queue.front());
queue.pop();
return true;
});
if (!result)
{
break;
}
this->handle(packet);
}
}
void tcp_server::send(const std::string& data)
{
out_queue_.access([&](stream_queue& queue)
{
for (const auto& val : data)
{
queue.push(val);
}
});
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "base_server.hpp"
#include <utils/concurrency.hpp>
namespace demonware
{
class tcp_server : public base_server
{
public:
using base_server::base_server;
void handle_input(const char* buf, size_t size);
size_t handle_output(char* buf, size_t size);
bool pending_data();
void frame() override;
protected:
virtual void handle(const std::string& data) = 0;
void send(const std::string& data);
private:
utils::concurrency::container<data_queue> in_queue_;
utils::concurrency::container<stream_queue> out_queue_;
};
}

View File

@ -0,0 +1,103 @@
#include <std_include.hpp>
#include "udp_server.hpp"
namespace demonware
{
void udp_server::handle_input(const char* buf, size_t size, endpoint_data endpoint)
{
in_queue_.access([&](in_queue& queue)
{
in_packet p;
p.data = std::string{buf, size};
p.endpoint = std::move(endpoint);
queue.emplace(std::move(p));
});
}
size_t udp_server::handle_output(SOCKET socket, char* buf, size_t size, sockaddr* address, int* addrlen)
{
return out_queue_.access<size_t>([&](socket_queue_map& map) -> size_t
{
const auto entry = map.find(socket);
if (entry == map.end())
{
return 0;
}
auto& queue = entry->second;
if (queue.empty())
{
return 0;
}
auto data = std::move(queue.front());
queue.pop();
const auto copy_size = std::min(size, data.data.size());
std::memcpy(buf, data.data.data(), copy_size);
std::memcpy(address, &data.address, sizeof(data.address));
*addrlen = sizeof(data.address);
return copy_size;
});
}
bool udp_server::pending_data(SOCKET socket)
{
return this->out_queue_.access<bool>([&](const socket_queue_map& map)
{
const auto entry = map.find(socket);
if (entry == map.end())
{
return false;
}
return !entry->second.empty();
});
}
void udp_server::send(const endpoint_data& endpoint, std::string data)
{
out_queue_.access([&](socket_queue_map& map)
{
out_packet p;
p.data = std::move(data);
p.address = endpoint.address;
map[endpoint.socket].emplace(std::move(p));
});
}
void udp_server::frame()
{
if (this->in_queue_.get_raw().empty())
{
return;
}
while (true)
{
in_packet packet{};
const auto result = this->in_queue_.access<bool>([&](in_queue& queue)
{
if (queue.empty())
{
return false;
}
packet = std::move(queue.front());
queue.pop();
return true;
});
if (!result)
{
break;
}
this->handle(packet.endpoint, std::move(packet.data));
}
}
}

View File

@ -0,0 +1,62 @@
#pragma once
#include "base_server.hpp"
#include <utils/concurrency.hpp>
namespace demonware
{
class udp_server : public base_server
{
public:
struct endpoint_data
{
SOCKET socket{};
sockaddr_in address{};
endpoint_data() = default;
endpoint_data(const SOCKET sock, const sockaddr* addr, const int size)
{
if (size != sizeof(this->address))
{
throw std::runtime_error("Invalid size");
}
this->socket = sock;
std::memcpy(&this->address, addr, sizeof(this->address));
}
};
using base_server::base_server;
void handle_input(const char* buf, size_t size, endpoint_data endpoint);
size_t handle_output(SOCKET socket, char* buf, size_t size, sockaddr* address, int* addrlen);
bool pending_data(SOCKET socket);
void frame() override;
protected:
virtual void handle(const endpoint_data& endpoint, const std::string& data) = 0;
void send(const endpoint_data& endpoint, std::string data);
private:
struct in_packet
{
std::string data;
endpoint_data endpoint;
};
struct out_packet
{
std::string data;
sockaddr_in address;
};
using in_queue = std::queue<in_packet>;
using out_queue = std::queue<out_packet>;
using socket_queue_map = std::unordered_map<SOCKET, out_queue>;
utils::concurrency::container<in_queue> in_queue_;
utils::concurrency::container<socket_queue_map> out_queue_;
};
}

View File

@ -0,0 +1,11 @@
#include <std_include.hpp>
#include "umbrella_server.hpp"
namespace demonware
{
void umbrella_server::handle(const std::string& packet)
{
// TODO:
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "tcp_server.hpp"
namespace demonware
{
class umbrella_server : public tcp_server
{
public:
using tcp_server::tcp_server;
private:
void handle(const std::string& packet) override;
};
}