World layers: a server's catalog must be the supported build's, and a player is not put out over one that is not

2.0.8's dedicated server was released with a world-layers.json scanned from
a game Steam had updated: 263 layers, where every player's game has 261. A
server that shares its layers (world_layer_sync) sends one choice for each,
the player's game turned the message down as unreadable, and nobody could
join such a server ("Join code, sender identity, or multiplayer protocol
did not match", and "Disconnected." in the server's log).
- supported_build::world_layer_count (261): how many layers the supported
  build's levels have.
- Server: a world-layers.json of another count is not used; the log says
  why and layer sync stays off, where before every player was turned away.
- --export-world-layers refuses a game folder whose count differs, so a
  release cannot be built from an updated game again.
- Player's game: a layer list of another length is left unread and every
  layer stays as the player has it; the rest of the message is taken and
  they join.
This commit is contained in:
AyeZeeBB committed 2026-10-13 18:45:37 -05:00
1 parent 9f93c3cbb2
commit 6c45e9666d
4 files changed
+38 -6

No files matched your search

+7
View File
@@ -1,6 +1,7 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <string_view>
@@ -27,6 +28,12 @@ inline constexpr std::string_view steam_build_id = "25414733";
// what mods made for it carry too. Data of another number beside a supported Skate.exe is a
// game that was updated or copied over in part, and has to be verified.
inline constexpr std::string_view game_data_changelist = "29544144";
// How many world layers this build's levels have: the catalog players scan from their game and
// a dedicated server reads from world-layers.json. A session's layers are sent as one choice for
// each, in the catalog's order, so both ends must have the same catalog. (2.0.8's server was
// released with the catalog of a game Steam had updated, 263 layers, and no player could join a
// server that shared its layers: 2026-10-13.)
inline constexpr std::size_t world_layer_count = 261;
// The live game's HTTP content cache for this build (catalogue chunks and CDN
// images), published as a ReSkateCache release and installed by the launcher.
inline constexpr std::wstring_view content_cache_url =
+9 -4
View File
@@ -1181,13 +1181,18 @@ std::optional<Packet> decode(std::span<const std::uint8_t> bytes) noexcept {
if (forced > 1) return {};
p.force_world_layers = forced != 0;
const auto layer_count = r.integer(2);
if (layer_count != world_layers().size() && (layer_count || p.force_world_layers)) return {};
p.layers.resize(world_layers().size());
// A list that is not one choice for each of this game's layers is of another catalog
// (a server whose world-layers.json is of another version of the game): which layer
// each choice is for cannot be told, so none is taken and every layer stays as the
// player has it. Turning the message down put the player out of the session as they
// joined, with nothing to say why (2.0.8's servers, 2026-10-13).
const bool same_catalog = layer_count == world_layers().size();
if (!same_catalog) p.force_world_layers = false;
p.layers.assign(world_layers().size(), 0);
for (std::uint64_t i = 0; i < layer_count; ++i) {
auto &choice = p.layers[static_cast<std::size_t>(i)];
const auto mode = r.integer(1);
if (mode >= world_layer_modes.size()) return {};
choice = static_cast<std::uint8_t>(mode);
if (same_catalog) p.layers[static_cast<std::size_t>(i)] = static_cast<std::uint8_t>(mode);
}
const auto voice_allowed = r.integer(1);
p.voice_policy.revision = static_cast<std::uint32_t>(r.integer(4));
+15 -1
View File
@@ -2,6 +2,7 @@
// in-game server browser. Runs from its own folder, next to steam_api64.dll and
// the Steam client files (steamclient64.dll, tier0_s64.dll, vstdlib_s64.dll).
// On Linux the Steam files are libsteam_api.so and steamclient.so.
#include "Engine/Game/Build/supported_build.h"
#include "global_bans.h"
#include "server_map_update.h"
#include "server_config.h"
@@ -276,6 +277,10 @@ int run(int argc, wchar_t **argv, bool skip_update) {
try {
const auto catalog = world_layer_scan::scan(argv[2]);
if (catalog.layers.empty()) throw std::runtime_error("no world layers found; is that the Skate folder?");
if (catalog.layers.size() != supported_build::world_layer_count)
throw std::runtime_error("that game has " + std::to_string(catalog.layers.size()) + " world layers, and the build ReSkate supports has " +
std::to_string(supported_build::world_layer_count) +
": it is another version of skate. (one Steam has updated?). Give the folder ReSkate's own game is in");
std::ofstream out(argv[3], std::ios::binary | std::ios::trunc);
out << world_layer_scan::to_json(catalog, "server");
if (!out) throw std::runtime_error("cannot write the file");
@@ -303,6 +308,10 @@ int run(int argc, char **argv, bool skip_update) {
try {
const auto catalog = world_layer_scan::scan(std::filesystem::path(argv[2]));
if (catalog.layers.empty()) throw std::runtime_error("no world layers found; is that the Skate folder?");
if (catalog.layers.size() != supported_build::world_layer_count)
throw std::runtime_error("that game has " + std::to_string(catalog.layers.size()) + " world layers, and the build ReSkate supports has " +
std::to_string(supported_build::world_layer_count) +
": it is another version of skate. (one Steam has updated?). Give the folder ReSkate's own game is in");
std::ofstream out(std::filesystem::path(argv[3]), std::ios::binary | std::ios::trunc);
out << world_layer_scan::to_json(catalog, "server");
if (!out) throw std::runtime_error("cannot write the file");
@@ -383,7 +392,12 @@ int run(int argc, char **argv, bool skip_update) {
// World layers are optional: without the players' catalog every player keeps their own.
if (const auto catalog = here / "world-layers.json"; std::filesystem::exists(catalog)) {
try {
install_world_layer_catalog(world_layer_scan::read(catalog));
auto layers = world_layer_scan::read(catalog);
// (Players would turn down every message that carries the layers, and could not join.)
if (layers.layers.size() != supported_build::world_layer_count)
throw std::runtime_error("it has " + std::to_string(layers.layers.size()) + " layers and players' games have " +
std::to_string(supported_build::world_layer_count) + ": it is of another version of skate. Take the one from this server's release");
install_world_layer_catalog(std::move(layers));
write_log("World layers: " + std::to_string(world_layers().size()) + " from world-layers.json.");
} catch (const std::exception &e) {
write_log(std::string("world-layers.json is unreadable; world layer sync is off: ") + e.what());
+7 -1
View File
@@ -1,3 +1,4 @@
#include "Engine/Game/Build/supported_build.h"
#include "server_engine.h"
#include "global_bans.h"
#include "server_map_update.h"
@@ -198,7 +199,12 @@ bool ServerEngine::load_environment(const std::filesystem::path& folder, const s
if (const auto catalog = folder_ / "world-layers.json"; std::filesystem::exists(catalog)) {
try {
install_world_layer_catalog(world_layer_scan::read(catalog));
auto layers = world_layer_scan::read(catalog);
// (Players would turn down every message that carries the layers, and could not join.)
if (layers.layers.size() != supported_build::world_layer_count)
throw std::runtime_error("it has " + std::to_string(layers.layers.size()) + " layers and players' games have " +
std::to_string(supported_build::world_layer_count) + ": it is of another version of skate. Take the one from this server's release; world layer sync is off");
install_world_layer_catalog(std::move(layers));
log("World layers: " + std::to_string(world_layers().size()) + " from world-layers.json.");
} catch (const std::exception &e) {
log(std::string("world-layers.json error: ") + e.what(), LogLevel::warning);