Theater server emulation

This commit is contained in:
project-bo4 2023-11-10 14:08:30 -08:00
parent e584075243
commit b77d42d869
7 changed files with 631 additions and 14 deletions

View File

@ -9,6 +9,7 @@
#include "demonware/servers/auth3_server.hpp"
#include "demonware/servers/stun_server.hpp"
#include "demonware/servers/umbrella_server.hpp"
#include "demonware/servers/fileshare_server.hpp"
#include "demonware/server_registry.hpp"
#define TCP_BLOCKING true
@ -477,6 +478,7 @@ namespace demonware
tcp_servers.create<auth3_server>("ops4-pc-auth3.prod.demonware.net");
tcp_servers.create<lobby_server>("ops4-pc-lobby.prod.demonware.net");
tcp_servers.create<umbrella_server>("prod.umbrella.demonware.net");
tcp_servers.create<fileshare_server>("ops4-fileshare.prod.schild.net");
}
void pre_start() override

View File

@ -0,0 +1,268 @@
#include <std_include.hpp>
#include "fileshare.hpp"
#include "component/platform.hpp"
#include <utilities/io.hpp>
#include <utilities/string.hpp>
#include <utilities/cryptography.hpp>
namespace demonware::fileshare
{
const char* get_fileshare_host_name()
{
return "ops4-fileshare.prod.schild.net";
}
const char* get_category_extension(fileshareCategory_e cat)
{
switch (cat)
{
case fileshareCategory_e::FILESHARE_CATEGORY_FILM:
return "demo";
case fileshareCategory_e::FILESHARE_CATEGORY_SCREENSHOT_PRIVATE:
return "jpg";
default:
return "";
}
}
fileshareCategory_e get_extension_category(const char* ext)
{
if (!strcmp(ext, "demo")) {
return fileshareCategory_e::FILESHARE_CATEGORY_FILM;
}
else if (!strcmp(ext, "jpg")) {
return fileshareCategory_e::FILESHARE_CATEGORY_SCREENSHOT_PRIVATE;
}
else {
return fileshareCategory_e::FILESHARE_CATEGORY_ALL;
}
}
std::string get_fileshare_directory()
{
return std::format("players/fileshare-{}", platform::bnet_get_userid());
}
std::string get_file_name(const uint64_t fileID, fileshareCategory_e category)
{
const char* extension = get_category_extension(category);
if(!extension || !strlen(extension)) return std::to_string(fileID);
return std::format("{}.{}", fileID, extension);
}
std::string get_file_url(const std::string& file)
{
return std::format("http://{}/{}", get_fileshare_host_name(), utilities::io::file_name(file));
}
std::string get_file_path(const std::string& file)
{
return std::format("{}/{}", get_fileshare_directory(), utilities::io::file_name(file));
}
std::string get_metadata_path(const std::string& file)
{
return std::format("{}/{}.metadata", get_fileshare_directory(), utilities::io::file_stem(file));
}
std::string FileMetadata::SerializeMetaJSON()
{
rapidjson::Document jsonDocument(rapidjson::kObjectType);
rapidjson::Document::AllocatorType& allocator = jsonDocument.GetAllocator();
jsonDocument.AddMember("status", this->status, allocator);
jsonDocument.AddMember("category", this->category, allocator);
jsonDocument.AddMember("fileName", this->fileName, allocator);
if (this->status == FILE_STATUS_UPLOADED || this->status == FILE_STATUS_DESCRIBED) {
jsonDocument.AddMember("fileSize", this->fileSize, allocator);
}
rapidjson::Value fileInfo(rapidjson::kObjectType);
fileInfo.AddMember("id", this->file.id, allocator);
fileInfo.AddMember("name", this->file.name, allocator);
if (this->status == FILE_STATUS_UPLOADED || this->status == FILE_STATUS_DESCRIBED) {
fileInfo.AddMember("size", this->file.size, allocator);
}
fileInfo.AddMember("timestamp", this->file.timestamp, allocator);
jsonDocument.AddMember("file", fileInfo, allocator);
rapidjson::Value fileAuthor(rapidjson::kObjectType);
fileAuthor.AddMember("name", this->author.name, allocator);
fileAuthor.AddMember("xuid", this->author.xuid, allocator);
jsonDocument.AddMember("author", fileAuthor, allocator);
if (this->status == FILE_STATUS_DESCRIBED) {
rapidjson::Value fileTags(rapidjson::kObjectType);
for (const auto& tag : this->tags)
{
rapidjson::Value key(std::to_string(tag.first), allocator);
fileTags.AddMember(key, tag.second, allocator);
}
jsonDocument.AddMember("tags", fileTags, allocator);
jsonDocument.AddMember("metadata", utilities::cryptography::base64::encode(ddl_metadata), allocator);
}
rapidjson::StringBuffer strbuf;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(strbuf);
jsonDocument.Accept(writer);
return std::string(strbuf.GetString(), strbuf.GetLength());
}
bool FileMetadata::ParseMetaJSON(const std::string& MetaDoc)
{
rapidjson::Document jsonDocument;
jsonDocument.Parse(MetaDoc);
if (jsonDocument.HasMember("status") && jsonDocument["status"].IsInt()) {
this->status = static_cast<file_status>(jsonDocument["status"].GetInt());
}
if (jsonDocument.HasMember("category") && jsonDocument["category"].IsInt()) {
this->category = static_cast<fileshareCategory_e>(jsonDocument["category"].GetInt());
}
if (jsonDocument.HasMember("fileName") && jsonDocument["fileName"].IsString()) {
this->fileName = jsonDocument["fileName"].GetString();
}
if ((this->status == FILE_STATUS_UPLOADED || this->status == FILE_STATUS_DESCRIBED)
&& jsonDocument.HasMember("fileSize") && jsonDocument["fileSize"].IsUint64())
{
this->fileSize = jsonDocument["fileSize"].GetUint64();
}
if (jsonDocument.HasMember("file") && jsonDocument["file"].IsObject())
{
rapidjson::Value& fileInfo = jsonDocument["file"];
if (fileInfo.HasMember("id") && fileInfo["id"].IsUint64()
&& fileInfo.HasMember("name") && fileInfo["name"].IsString()
&& fileInfo.HasMember("timestamp") && fileInfo["timestamp"].IsUint())
{
this->file.id = fileInfo["id"].GetUint64();
this->file.name = fileInfo["name"].GetString();
this->file.timestamp = fileInfo["timestamp"].GetUint();
}
if ((this->status == FILE_STATUS_UPLOADED || this->status == FILE_STATUS_DESCRIBED)
&& fileInfo.HasMember("size") && fileInfo["size"].IsUint())
{
this->file.size = fileInfo["size"].GetUint();
}
}
if (jsonDocument.HasMember("author") && jsonDocument["author"].IsObject())
{
rapidjson::Value& fileAuthor = jsonDocument["author"];
if (fileAuthor.HasMember("name") && fileAuthor["name"].IsString()
&& fileAuthor.HasMember("xuid") && fileAuthor["xuid"].IsUint64())
{
this->author.name = fileAuthor["name"].GetString();
this->author.xuid = fileAuthor["xuid"].GetUint64();
}
}
if (this->status == FILE_STATUS_DESCRIBED) {
if (jsonDocument.HasMember("tags") && jsonDocument["tags"].IsObject())
{
rapidjson::Value& fileTags = jsonDocument["tags"];
for (rapidjson::Value::ConstMemberIterator itr =
fileTags.MemberBegin(); itr != fileTags.MemberEnd(); ++itr)
{
this->tags.insert({ atoi(itr->name.GetString()), itr->value.GetUint64() });
}
}
if (jsonDocument.HasMember("metadata") && jsonDocument["metadata"].IsString())
{
rapidjson::Value& ddl_field = jsonDocument["metadata"];
std::string metadata_b64(ddl_field.GetString(), ddl_field.GetStringLength());
this->ddl_metadata = utilities::cryptography::base64::decode(metadata_b64);
}
}
if (this->status == FILE_STATUS_DESCRIBED) {
return (this->fileName.size() && this->file.id && this->ddl_metadata.size());
}
else if (this->status == FILE_STATUS_UPLOADED) {
return (this->fileName.size() && this->file.id && this->fileSize);
}
else {
return (this->fileName.size() && this->file.id && this->file.name.size());
}
}
bool FileMetadata::MetadataTaskResult(bdFileMetaData* output, bool download)
{
if (this->status == FILE_STATUS_DESCRIBED)
{
output->m_fileID = this->file.id;
output->m_createTime = this->file.timestamp;
output->m_modifedTime = output->m_createTime;
output->m_fileSize = this->fileSize;
output->m_ownerID = this->author.xuid;
output->m_ownerName = this->author.name;
output->m_fileSlot = 0;
output->m_fileName = this->file.name;
output->m_category = this->category;
output->m_metaData = this->ddl_metadata;
output->m_summaryFileSize = 0; // what!?
if (download) {
output->m_url = get_file_url(this->fileName);
}
else {
output->m_tags = this->tags;
}
return true;
}
else {
return false;
}
}
bool FileMetadata::ReadMetaDataJson(const std::string& file, file_status expect)
{
std::string json;
if (utilities::io::read_file(file, &json))
{
return (this->ParseMetaJSON(json)
&& (this->status == expect || expect == FILE_STATUS_UNKNOWN));
}
else {
return false;
}
}
bool FileMetadata::WriteMetaDataJson(const std::string& file, file_status status)
{
if(status != FILE_STATUS_UNKNOWN) this->status = status;
return utilities::io::write_file(file, this->SerializeMetaJSON());
}
std::vector<uint64_t> fileshare_list_demo_ids()
{
std::vector<uint64_t> results;
std::vector<std::string> files = utilities::io::list_files(get_fileshare_directory());
for (auto& file : files)
{
std::string rawName = utilities::io::file_stem(file);
if (utilities::string::is_integer(rawName)
&& utilities::string::ends_with(file, ".demo")
&& utilities::io::file_exists(get_metadata_path(rawName)))
{
results.push_back(std::stoull(rawName));
}
}
return results;
}
}

View File

@ -0,0 +1,103 @@
#pragma once
#include "data_types.hpp"
namespace demonware::fileshare
{
const char* get_fileshare_host_name();
enum fileshareCategory_e
{
FILESHARE_CATEGORY_ALL = 0,
FILESHARE_CATEGORY_CONTENT_SERVER_START = 1,
FILESHARE_CATEGORY_EMBLEM = 1,
FILESHARE_CATEGORY_PAINTJOB = 2,
FILESHARE_CATEGORY_VARIANT = 3,
FILESHARE_CATEGORY_DECAL = 4,
FILESHARE_CATEGORY_GUNRENDER = 5,
FILESHARE_CATEGORY_CLIP = 6,
FILESHARE_CATEGORY_EMBLEMIMAGE = 7,
FILESHARE_CATEGORY_FILM = 10,
FILESHARE_CATEGORY_SCREENSHOT = 11,
FILESHARE_CATEGORY_CUSTOM_GAME_MODE = 12,
FILESHARE_CATEGORY_CONTENT_SERVER_END = 12,
FILESHARE_CATEGORY_CLIP_PRIVATE = 14,
FILESHARE_CATEGORY_SCREENSHOT_PRIVATE = 15,
FILESHARE_CATEGORY_CUSTOM_GAME_MODE_PRIVATE = 16,
FILESHARE_CATEGORY_INGAMESTORE_START = 100,
FILESHARE_CATEGORY_INGAMESTORE_MAPPACKS = 100,
FILESHARE_CATEGORY_INGAMESTORE_THEMES = 101,
FILESHARE_CATEGORY_INGAMESTORE_AVATARS = 102,
FILESHARE_CATEGORY_INGAMESTORE_WEAPONPACKS = 103,
FILESHARE_CATEGORY_INGAMESTORE_CALLINGCARDPACKS = 104,
FILESHARE_CATEGORY_INGAMESTORE_STORAGEPACKS = 105,
FILESHARE_CATEGORY_INGAMESTORE_END = 105,
FILESHARE_CATEGORY_MOTD_IMAGES = 120,
FILESHARE_CATEGORY_VOTE_IMAGES = 130,
FILESHARE_CATEGORY_MDLC = 1000,
FILESHARE_CATEGORY_MDLC_DEDICATED = 1001,
FILESHARE_CATEGORY_MDLC_LAST = 1999,
FILESHARE_CATEGORY_AVI = 32768,
FILESHARE_CATEGORY_EXEMONITOR = 32769,
FILESHARE_CATEGORY_INVALID = -1
};
const char* get_category_extension(fileshareCategory_e cat);
fileshareCategory_e get_extension_category(const char* ext);
std::string get_fileshare_directory();
std::string get_file_name(const uint64_t fileID,
fileshareCategory_e category = FILESHARE_CATEGORY_INVALID);
std::string get_file_url(const std::string& file);
std::string get_file_path(const std::string& file);
std::string get_metadata_path(const std::string& file);
class FileMetadata final
{
public:
FileMetadata() = default;
~FileMetadata() = default;
enum file_status
{
FILE_STATUS_UNKNOWN = 0,
FILE_STATUS_INVALID = -1,
FILE_STATUS_UPLOADING = 1,
FILE_STATUS_UPLOADED = 2,
FILE_STATUS_DESCRIBED = 3
} status{};
struct file_info
{
uint64_t id;
std::string name;
uint32_t size;
uint32_t timestamp;
} file{};
struct author_info
{
uint64_t xuid;
std::string name;
} author{};
fileshareCategory_e category = FILESHARE_CATEGORY_ALL;
std::string fileName = "";
size_t fileSize = 0;
std::string ddl_metadata = "";
std::map<uint64_t, uint64_t> tags;
bool MetadataTaskResult(bdFileMetaData* output, bool download);
bool ReadMetaDataJson(const std::string& file, file_status expect = FILE_STATUS_UNKNOWN);
bool WriteMetaDataJson(const std::string& file, file_status status = FILE_STATUS_UNKNOWN);
private:
std::string SerializeMetaJSON();
bool ParseMetaJSON(const std::string& input);
};
std::vector<uint64_t> fileshare_list_demo_ids();
}

View File

@ -0,0 +1,111 @@
#include <std_include.hpp>
#include "fileshare_server.hpp"
#include "../fileshare.hpp"
#include <utilities/string.hpp>
#include <utilities/io.hpp>
std::string fileshare_upload_file_name{};
std::vector<byte> fileshare_upload_buffer{};
namespace demonware
{
void fileshare_server::handle(const std::string& packet)
{
static bool upload_in_progress = false;
if (packet.starts_with("GET"))
{
std::string file = utilities::string::split(utilities::string::split(packet, '\n')[0], ' ')[1].substr(1, std::string::npos);
this->send(download_file(file));
}
else if (packet.starts_with("PUT"))
{
fileshare_upload_file_name = utilities::string::split(utilities::string::split(packet, '\n')[0], ' ')[1].substr(1, std::string::npos);
fileshare_upload_buffer.clear();
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [fileshare]: upload stream started for '%s'\n", fileshare_upload_file_name.data());
this->send("");
upload_in_progress = true;
}
else
{
if (!upload_in_progress)
{
std::string http_response = "HTTP/1.1 501 Not Implemented\r\n";
http_response.append("Connection: close\r\n\r\n");
return this->send(http_response);
}
std::string chunk_header = utilities::string::split(packet, "\r\n")[0];
size_t chunk_length = std::stoul(chunk_header, nullptr, 16);
if (chunk_length != 0)
{
fileshare_upload_buffer.insert(fileshare_upload_buffer.end(), &packet[chunk_header.length() + 2], &packet[chunk_header.length() + 2 + chunk_length]);
}
else
{
std::string response = finalize_upload_file(fileshare_upload_file_name, fileshare_upload_buffer);
upload_in_progress = false;
this->send(response);
}
}
}
std::string fileshare_server::http_header_time()
{
// 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);
return std::format("{} GMT", date);
}
std::string fileshare_server::download_file(const std::string& file)
{
std::string http_response{}, file_buffer{};
if (utilities::io::read_file(fileshare::get_file_path(file), &file_buffer))
{
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [fileshare]: hosting requested file '%s'\n", file.data());
http_response.append("HTTP/1.1 200 OK\r\n");
http_response.append("Server: Apache/0.0.0 (Win64)\r\n");
http_response.append("Content-Type: application/octet-stream\r\n");
http_response.append(utilities::string::va("Date: %s\r\n", http_header_time().data()));
http_response.append(utilities::string::va("Content-Length: %d\r\n\r\n", file_buffer.length()));
http_response.append(file_buffer);
}
else
{
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [fileshare]: couldnt find requested file '%s'\n", file.data());
file_buffer = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>";
http_response.append("HTTP/1.1 404 Not Found\r\n");
http_response.append("Server: Apache/0.0.0 (Win64)\r\n");
http_response.append("Content-Type: text/html\r\n");
http_response.append(utilities::string::va("Date: %s\r\n", http_header_time().data()));
http_response.append(utilities::string::va("Content-Length: %d\r\n\r\n", file_buffer.length()));
}
return http_response;
}
std::string fileshare_server::finalize_upload_file(const std::string& file, const std::vector<byte>& buffer)
{
if(utilities::io::write_file(fileshare::get_file_path(file), std::string(buffer.begin(), buffer.end())))
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [fileshare]: file upload finalized; saved as '%s'\n", fileshare_upload_file_name.data());
else
logger::write(logger::LOG_TYPE_DEBUG, "[DW]: [fileshare]: error on saving uploaded file '%s'\n", fileshare_upload_file_name.data());
return "HTTP/1.1 201 Created\r\nContent-Length: 0\r\n\r\n";
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "tcp_server.hpp"
namespace demonware
{
class fileshare_server : public tcp_server
{
public:
using tcp_server::tcp_server;
private:
void handle(const std::string& packet) override;
std::string http_header_time();
std::string download_file(const std::string& file);
std::string finalize_upload_file(const std::string& file, const std::vector<byte>& buffer);
};
}

View File

@ -1,5 +1,9 @@
#include <std_include.hpp>
#include "../services.hpp"
#include "../fileshare.hpp"
#include "component/platform.hpp"
#include <utilities/io.hpp>
namespace demonware
{
@ -18,24 +22,90 @@ namespace demonware
this->register_task(22, &bdPooledStorage::_preDownloadMultiPart);
}
void bdPooledStorage::getPooledMetaDataByID(service_server* server, byte_buffer* /*buffer*/) const
void bdPooledStorage::getPooledMetaDataByID(service_server* server, byte_buffer* buffer) const
{
// TODO:
std::vector<uint64_t> requested_files;
buffer->read_array(10, &requested_files);
auto reply = server->create_reply(this->task_id());
for (auto fileID : requested_files)
{
std::string metafile = fileshare::get_metadata_path(fileshare::get_file_name(fileID));
fileshare::FileMetadata metadata;
if (metadata.ReadMetaDataJson(metafile, metadata.FILE_STATUS_DESCRIBED)
&& utilities::io::file_exists(fileshare::get_file_path(metadata.fileName)))
{
auto taskResult = new bdFileMetaData;
metadata.MetadataTaskResult(taskResult, false);
reply->add(taskResult);
}
}
reply->send();
}
void bdPooledStorage::_preUpload(service_server* server, byte_buffer* /*buffer*/) const
void bdPooledStorage::_preUpload(service_server* server, byte_buffer* buffer) const
{
// TODO:
std::string filename; uint16_t category;
buffer->read_string(&filename);
buffer->read_uint16(&category);
uint32_t timestamp = static_cast<uint32_t>(time(nullptr));
fileshare::FileMetadata metadata;
metadata.file.id = timestamp;
metadata.file.name = filename;
metadata.file.timestamp = timestamp;
metadata.author.xuid = platform::bnet_get_userid();
metadata.author.name = platform::bnet_get_username();
metadata.category = static_cast<fileshare::fileshareCategory_e>(category);
metadata.fileName = fileshare::get_file_name(metadata.file.id, metadata.category);
metadata.WriteMetaDataJson(fileshare::get_metadata_path(metadata.fileName), metadata.FILE_STATUS_UPLOADING);
auto reply = server->create_reply(this->task_id());
auto result = new bdURL;
result->m_url = fileshare::get_file_url(metadata.fileName);
result->m_serverType = 8;
result->m_serverIndex = "fs";
result->m_fileID = metadata.file.id;
reply->add(result);
reply->send();
}
void bdPooledStorage::_postUploadFile(service_server* server, byte_buffer* /*buffer*/) const
void bdPooledStorage::_postUploadFile(service_server* server, byte_buffer* buffer) const
{
// TODO:
uint64_t fileID; uint32_t fileSize;
uint16_t serverType; std::string serverIndex;
buffer->read_uint64(&fileID);
buffer->read_uint16(&serverType);
buffer->read_string(&serverIndex);
buffer->read_uint32(&fileSize);
auto metafile = fileshare::get_metadata_path(fileshare::get_file_name(fileID));
fileshare::FileMetadata metadata;
if (metadata.ReadMetaDataJson(metafile)) {
metadata.file.size = fileSize;
metadata.fileSize = utilities::io::file_size(fileshare::get_file_path(metadata.fileName));
metadata.WriteMetaDataJson(metafile, metadata.FILE_STATUS_UPLOADED);
}
auto reply = server->create_reply(this->task_id());
auto result = new bdUInt64Result;
result->value = fileID;
reply->add(result);
reply->send();
}
@ -48,21 +118,53 @@ namespace demonware
void bdPooledStorage::_preDownload(service_server* server, byte_buffer* buffer) const
{
// TODO:
auto reply = server->create_reply(this->task_id());
reply->send();
uint64_t fileID;
buffer->read_uint64(&fileID);
std::string metafile = fileshare::get_metadata_path(fileshare::get_file_name(fileID));
fileshare::FileMetadata metadata;
if (metadata.ReadMetaDataJson(metafile, metadata.FILE_STATUS_DESCRIBED)
&& utilities::io::file_exists(fileshare::get_file_path(metadata.fileName)))
{
auto reply = server->create_reply(this->task_id());
auto taskResult = new bdFileMetaData;
metadata.MetadataTaskResult(taskResult, true);
reply->add(taskResult);
reply->send();
}
else
{
auto reply = server->create_reply(this->task_id(), 2000/*BD_CONTENTSTREAMING_FILE_NOT_AVAILABLE*/);
reply->send();
}
}
void bdPooledStorage::_preUploadSummary(service_server* server, byte_buffer* /*buffer*/) const
void bdPooledStorage::_preUploadSummary(service_server* server, byte_buffer* buffer) const
{
// TODO:
auto reply = server->create_reply(this->task_id());
uint64_t fileID{}; uint32_t fileSize{};
//std::string DDL_MetaData;
buffer->read_uint64(&fileID);
buffer->read_uint32(&fileSize);
auto metafile = fileshare::get_metadata_path(fileshare::get_file_name(fileID));
fileshare::FileMetadata metadata;
if (metadata.ReadMetaDataJson(metafile)) {
buffer->read_blob(&metadata.ddl_metadata);
buffer->read_array(10, &metadata.tags);
metadata.WriteMetaDataJson(metafile, metadata.FILE_STATUS_DESCRIBED);
}
auto reply = server->create_reply(this->task_id(), 108/*BD_SERVICE_NOT_AVAILABLE*/);
reply->send();
}
void bdPooledStorage::_postUploadSummary(service_server* server, byte_buffer* /*buffer*/) const
{
// TODO:
auto reply = server->create_reply(this->task_id());
reply->send();
}

View File

@ -1,5 +1,6 @@
#include <std_include.hpp>
#include "../services.hpp"
#include "../fileshare.hpp"
namespace demonware
{
@ -42,8 +43,19 @@ namespace demonware
void bdTags::searchByTagsBase(service_server* server, byte_buffer* /*buffer*/) const
{
// TODO:
std::vector<uint64_t> demo_ids = fileshare::fileshare_list_demo_ids();
auto reply = server->create_reply(this->task_id());
for (auto id : demo_ids)
{
auto result = new bdUInt64Result;
result->value = id;
reply->add(result);
}
logger::write(logger::LOG_TYPE_DEBUG, "[bdTags::searchByTagsBase] Listed Total %u Demos", static_cast<uint32_t>(demo_ids.size()));
reply->send();
}
}