Generate MT 0.4 character.b3d files at load time

These generated files are kept in RAM and not on disk.
master
luk3yx 2022-01-28 12:22:13 +13:00 committed by GitHub
parent 26a5aed0c4
commit dae831a223
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 11 deletions

View File

@ -369,6 +369,8 @@ void set_default_settings()
// Server
settings->setDefault("compat_player_model", "character.b3d,3d_armor_character.b3d,skinsdb_3d_armor_character_5.b3d");
settings->setDefault("compat_send_original_model", "true");
settings->setDefault("disable_escape_sequences", "false");
settings->setDefault("strip_color_codes", "false");
#if USE_PROMETHEUS

View File

@ -418,6 +418,17 @@ void Server::init()
m_modmgr->loadMods(m_script);
// m_compat_player_models is used to prevent constant re-parsing of the
// setting
std::string player_models = g_settings->get("compat_player_model");
player_models.erase(std::remove_if(player_models.begin(),
player_models.end(), static_cast<int(*)(int)>(&std::isspace)),
player_models.end());
if (player_models.empty() || isSingleplayer())
FATAL_ERROR_IF(!m_compat_player_models.empty(), "Compat player models list not empty");
else
m_compat_player_models = str_split(player_models, ',');
// Read Textures and calculate sha1 sums
fillMediaCache();
@ -2495,6 +2506,76 @@ bool Server::SendBlock(session_t peer_id, const v3s16 &blockpos)
return true;
}
// Hacks because I don't want to make duplicate read/write functions for little
// endian numbers.
u32 readU32_le(std::istream &is) {
char buf[4] = {0};
is.read(buf, sizeof(buf));
std::reverse(buf, buf + sizeof(buf));
return readU32((u8 *)buf);
}
v3f readV3F32_le(std::istream &is) {
char buf[12] = {0};
is.read(buf, sizeof(buf));
std::reverse(buf, buf + 4);
std::reverse(buf + 4, buf + 8);
std::reverse(buf + 8, buf + 12);
return readV3F32((u8 *)buf);
}
void writeV3F32_le(std::ostream &os, v3f pos) {
char buf[12];
writeV3F32((u8 *)buf, pos);
std::reverse(buf, buf + 4);
std::reverse(buf + 4, buf + 8);
std::reverse(buf + 8, buf + 12);
os.write(buf, sizeof(buf));
}
// Converts MT 5+ player models into MT 0.4 compatible models
std::string makeCompatPlayerModel(std::string b3d) {
std::stringstream ss(b3d);
// ss.read(4) != "BB3D"
const u32 header = readU32_le(ss);
if (header != 0x44334242) {
warningstream << "Invalid B3D header in player model: " << header << std::endl;
return "";
}
readU32(ss); // Length
readU32(ss); // Version
// Look for the node
while (ss.good()) {
const u32 name = readU32_le(ss);
const u32 length = readU32_le(ss);
// name != "NODE"
if (name != 0x45444f4e) {
ss.ignore(length);
continue;
}
// Node name
ss.ignore(length, '\x00');
// Node position
std::streampos p = ss.tellg();
const v3f offset_pos = readV3F32_le(ss) - v3f(0, BS, 0);
// Write the new position back to the stringstream
ss.seekp(p);
writeV3F32_le(ss, offset_pos);
return ss.str();
}
warningstream << "Could not find base position in B3D file" << std::endl;
return "";
}
bool Server::addMediaFile(const std::string &filename,
const std::string &filepath, std::string *filedata_to,
std::string *digest_to)
@ -2548,6 +2629,32 @@ bool Server::addMediaFile(const std::string &filename,
// Put in list
m_media[filename] = MediaInfo(filepath, sha1_base64);
// Add a compatibility model if required
if (isCompatPlayerModel(filename)) {
// Offset the mesh
const std::string filedata_compat = makeCompatPlayerModel(filedata);
if (filedata_compat != "") {
SHA1 sha1;
sha1.addBytes(filedata_compat.c_str(), filedata_compat.length());
unsigned char *digest = sha1.getDigest();
std::string sha1_base64 = base64_encode(digest, 20);
free(digest);
// If the original model is being sent then rename the
// compatibility one so it doesn't conflict. The renamed model is
// used in player_sao.cpp if the setting is enabled.
std::string fn_compat = filename;
if (g_settings->getBool("compat_send_original_model")) {
fn_compat = "_mc_compat_" + fn_compat;
// Add a dummy m_media entry
m_media[fn_compat] = MediaInfo("", "");
}
m_compat_media[fn_compat] = InMemoryMediaInfo(filedata_compat, sha1_base64);
}
}
if (filedata_to)
*filedata_to = std::move(filedata);
return true;
@ -2562,8 +2669,6 @@ void Server::fillMediaCache()
// The paths are ordered in descending priority
fs::GetRecursiveDirs(paths, porting::path_user + DIR_DELIM + "textures" + DIR_DELIM + "server");
fs::GetRecursiveDirs(paths, m_gamespec.path + DIR_DELIM + "textures");
fs::GetRecursiveDirs(paths, porting::path_share + DIR_DELIM + "builtin" +
DIR_DELIM + "game" + DIR_DELIM + "models");
m_modmgr->getModsMediaPaths(paths);
// Collect media file information from paths into cache
@ -2588,6 +2693,8 @@ void Server::fillMediaCache()
void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_code)
{
const u16 protocol_version = m_clients.getProtocolVersion(peer_id);
// Make packet
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
@ -2597,6 +2704,9 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
for (const auto &i : m_media) {
if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
continue;
// Skip dummy entries on 5.0+ clients
if (protocol_version >= 37 && i.second.sha1_digest.empty())
continue;
media_sent++;
}
@ -2605,7 +2715,18 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
for (const auto &i : m_media) {
if (str_ends_with(i.first, ".tr") && !str_ends_with(i.first, lang_suffix))
continue;
pkt << i.first << i.second.sha1_digest;
if (protocol_version >= 37 && i.second.sha1_digest.empty())
continue;
pkt << i.first;
if (protocol_version < 37 &&
m_compat_media.find(i.first) != m_compat_media.end()) {
pkt << m_compat_media[i.first].sha1_digest;
} else {
FATAL_ERROR_IF(i.second.sha1_digest.empty(), "Attempt to send dummy media");
pkt << i.second.sha1_digest;
}
}
pkt << g_settings->get("remote_media");
@ -2645,6 +2766,7 @@ void Server::sendRequestedMedia(session_t peer_id,
u32 file_size_bunch_total = 0;
const u16 protocol_version = m_clients.getProtocolVersion(peer_id);
for (const std::string &name : tosend) {
if (m_media.find(name) == m_media.end()) {
errorstream<<"Server::sendRequestedMedia(): Client asked for "
@ -2655,6 +2777,20 @@ void Server::sendRequestedMedia(session_t peer_id,
//TODO get path + name
std::string tpath = m_media[name].path;
// Use compatibility media on older clients
if (protocol_version < 37 &&
m_compat_media.find(name) != m_compat_media.end()) {
file_bunches[file_bunches.size()-1].emplace_back(name, tpath,
m_compat_media[name].data);
continue;
}
if (tpath.empty()) {
errorstream<<"Server::sendRequestedMedia(): New client asked for "
<<"compatibility media file \""<<(name)<<"\""<<std::endl;
continue;
}
// Read data
std::ifstream fis(tpath.c_str(), std::ios_base::binary);
if(!fis.good()){

View File

@ -91,6 +91,19 @@ struct MediaInfo
}
};
struct InMemoryMediaInfo
{
std::string data;
std::string sha1_digest;
InMemoryMediaInfo(const std::string &data_="",
const std::string &sha1_digest_=""):
data(data_),
sha1_digest(sha1_digest_)
{
}
};
struct ServerSoundParams
{
enum Type {
@ -376,6 +389,15 @@ public:
// Environment mutex (envlock)
std::mutex m_env_mutex;
inline bool isCompatPlayerModel(const std::string &model_name)
{
return std::find(m_compat_player_models.begin(), m_compat_player_models.end(), model_name) != m_compat_player_models.end();
}
const std::vector<std::string> getCompatPlayerModels()
{
return m_compat_player_models;
}
private:
friend class EmergeThread;
friend class RemoteClient;
@ -649,6 +671,7 @@ private:
// media files known to server
std::unordered_map<std::string, MediaInfo> m_media;
std::unordered_map<std::string, InMemoryMediaInfo> m_compat_media;
/*
Sounds
@ -682,6 +705,8 @@ private:
MetricCounterPtr m_aom_buffer_counter;
MetricCounterPtr m_packet_recv_counter;
MetricCounterPtr m_packet_recv_processed_counter;
std::vector<std::string> m_compat_player_models;
};
/*

View File

@ -592,13 +592,11 @@ std::string PlayerSAO::getPropertyPacket(const u16 protocol_version)
m_prop.is_visible = (true);
ObjectProperties prop = m_prop;
if (protocol_version < 37 && (m_prop.mesh == "3d_armor_character.b3d" ||
m_prop.mesh == "character.b3d" ||
m_prop.mesh == "skinsdb_3d_armor_character_5.b3d")) {
prop.mesh = "mc_compat_character.b3d";
for (u16 i = prop.textures.size(); i < 5; i++) {
prop.textures.emplace_back("blank.png");
}
// Use the renamed model if compat_send_original_model is enabled
if (protocol_version < 37 && m_env->getCompatSendOriginalModel() &&
m_env->isCompatPlayerModel(m_prop.mesh)) {
prop.mesh = "_mc_compat_" + m_prop.mesh;
}
// Remove a one-node offset from a copy of the object properties for MT 0.4

View File

@ -467,6 +467,9 @@ ServerEnvironment::ServerEnvironment(ServerMap *map,
m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
m_compat_send_original_model = !server->getCompatPlayerModels().empty() &&
g_settings->getBool("compat_send_original_model");
}
ServerEnvironment::~ServerEnvironment()
@ -899,7 +902,7 @@ public:
for (ActiveABM &aabm : *m_aabms[c]) {
if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
continue;
if (myrand() % aabm.chance != 0)
continue;
@ -2400,3 +2403,8 @@ bool ServerEnvironment::migrateAuthDatabase(
}
return true;
}
const bool ServerEnvironment::isCompatPlayerModel(const std::string &model_name)
{
return m_server->isCompatPlayerModel(model_name);
}

View File

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "settings.h"
#include "server/activeobjectmgr.h"
#include "util/numeric.h"
#include <algorithm>
#include <set>
#include <random>
@ -364,6 +365,9 @@ public:
AuthDatabase *getAuthDatabase() { return m_auth_database; }
static bool migrateAuthDatabase(const GameParams &game_params,
const Settings &cmd_args);
const bool isCompatPlayerModel(const std::string &model_name);
inline bool getCompatSendOriginalModel() { return m_compat_send_original_model; }
private:
/**
@ -478,5 +482,8 @@ private:
std::unordered_map<u32, float> m_particle_spawners;
std::unordered_map<u32, u16> m_particle_spawner_attachments;
std::vector<std::string> m_compat_player_models;
bool m_compat_send_original_model;
ServerActiveObject* createSAO(ActiveObjectType type, v3f pos, const std::string &data);
};