mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
feat(zone): zone files as the game client's level editor packs them
ZoneFilePack reads and writes the editor's packed files: named entries whose data is sd0, one entry for a single file or the zone, terrain, scenes and triggers of a whole map. Every length is checked, data that is not sd0 or has a chunk over 256 KiB is refused, and only a name's base name is used. ZoneFilePush makes a set of what was sent (one file replaces that file in the zone's set), checks it and applies it like any other set. The override store links a set's files instead of moving them, so a file the zone already has can be carried into its next set, and one change at a time is made in an override folder whichever server makes it. The asset manager can read the client's own file past an override. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
86
dCommon/ZoneFilePush.cpp
Normal file
86
dCommon/ZoneFilePush.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "ZoneFilePush.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "ZoneOverrideStore.h"
|
||||
|
||||
namespace {
|
||||
std::string BaseOf(const std::string& path) {
|
||||
const auto slash = path.find_last_of('/');
|
||||
return slash == std::string::npos ? path : path.substr(slash + 1);
|
||||
}
|
||||
}
|
||||
|
||||
ZoneFilePush::Result ZoneFilePush::Run(const Job& job) {
|
||||
Result result;
|
||||
const auto zoneFolder = ZonePublish::FolderOf(job.luzPath);
|
||||
try {
|
||||
ZoneFilePack::Files files;
|
||||
const ZoneFilePack::Limits packLimits{ job.limits.maxFileBytes, job.limits.maxTotalBytes, job.limits.maxFiles };
|
||||
if (const auto error = ZoneFilePack::Unpack(job.fileType, job.packedFiles, packLimits, files); error != ZoneFilePack::eError::NONE) {
|
||||
result.problem = std::string("the files could not be unpacked: ") + ZoneFilePack::Describe(error);
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto folder = ZoneOverrideStore::MakeUploadDir(job.root, job.id);
|
||||
std::vector<ZonePublish::Upload> uploads;
|
||||
std::set<std::string> sent;
|
||||
uint32_t index = 0;
|
||||
// A name is a base name here; it becomes a file of this zone's folder, and the checks decide whether the
|
||||
// zone has such a file. Its bytes are written under a name of our own.
|
||||
const auto add = [&](const ZoneFilePack::Entry& entry) {
|
||||
const auto path = zoneFolder + ZonePublish::Canonical(entry.name);
|
||||
const auto file = folder / ("file-" + std::to_string(index++));
|
||||
std::ofstream out(file, std::ios::binary | std::ios::trunc);
|
||||
out.write(entry.data.data(), static_cast<std::streamsize>(entry.data.size()));
|
||||
out.flush();
|
||||
if (!out) throw ZoneOverrideStore::StoreError("A file could not be written");
|
||||
uploads.push_back({ path, file, entry.data.size() });
|
||||
sent.insert(path);
|
||||
};
|
||||
if (files.zone) add(*files.zone);
|
||||
if (files.terrain) add(*files.terrain);
|
||||
for (const auto& scene : files.scenes) add(scene);
|
||||
for (const auto& triggers : files.triggers) add(triggers);
|
||||
|
||||
// One file replaces that file in the zone's set: the rest of the set stays as it is
|
||||
if (job.fileType != ZoneFilePack::eFileType::WHOLE_MAP) {
|
||||
for (const auto& current : ZoneOverrideStore::Current(job.root, job.zoneId, zoneFolder)) {
|
||||
if (sent.contains(current.path)) continue;
|
||||
const auto file = ZonePublish::ResolveInside(job.root / "maps", current.path);
|
||||
if (!file) continue;
|
||||
uploads.push_back({ current.path, *file, current.bytes });
|
||||
sent.insert(current.path);
|
||||
}
|
||||
}
|
||||
// Every set has the zone's .luz: the client's own when none was sent or kept
|
||||
if (!sent.contains(job.luzPath) && job.clientLuz) add({ BaseOf(job.luzPath), *job.clientLuz });
|
||||
|
||||
ZonePublish::Zone zone;
|
||||
zone.zoneId = job.zoneId;
|
||||
zone.luzPath = job.luzPath;
|
||||
zone.clientScenes = job.clientScenes;
|
||||
zone.hasFile = [&job](const std::string& path) { return job.clientFiles.contains(path); };
|
||||
const auto validation = ZonePublish::Validate(zone, uploads, job.limits);
|
||||
if (!validation.ok) {
|
||||
for (const auto& file : validation.files) {
|
||||
if (file.ok || file.errors.empty()) continue;
|
||||
result.problem = BaseOf(file.path) + ": " + file.errors.front().message + " (" + file.errors.front().code + ")";
|
||||
break;
|
||||
}
|
||||
ZoneOverrideStore::RemoveStaging(job.root, job.id);
|
||||
return result;
|
||||
}
|
||||
std::vector<ZoneOverrideStore::NewFile> newFiles;
|
||||
for (const auto& file : validation.files) newFiles.push_back({ file.path, file.source, file.bytes, file.sha256, file.version });
|
||||
const auto applied = ZoneOverrideStore::Apply(job.root, { job.zoneId, zoneFolder, job.id, job.who, "sent from the game client", job.now, job.keepBackups }, newFiles);
|
||||
result.applied = true;
|
||||
result.files = applied.files.size();
|
||||
result.backupId = applied.backupId;
|
||||
} catch (const std::exception& ex) {
|
||||
result.problem = ex.what();
|
||||
ZoneOverrideStore::RemoveStaging(job.root, job.id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user