mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Turn a 26.2 client away before untranslated registry data
Only packet ids are translated for an older client so far (#10404): the registry data, tags (registry ids) and play packets (chunks, light, login, player_action numbering...) would reach a real 26.2 client in 26.3 ids and layouts. With accept_older_clients on, such a client now logs in and is told so in configuration, right after the known packs and before any of them. NetConfig::older_clients_play (tests only, not in server.toml) keeps the play path reachable for the id renumbering test until the later parts translate the contents. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5daf0853e3
commit
bebf670623
6 files changed
+74
-16
No files matched your search
@@ -1123,6 +1123,7 @@ impl Game<'_> {
|
||||
.raw
|
||||
.get(horizon_client::lan::ACCEPT_OLDER_CLIENTS)
|
||||
.is_some_and(|v| v.trim().eq_ignore_ascii_case("true")),
|
||||
older_clients_play: false,
|
||||
lod_distance: horizon_client::lod_net::distance_property(
|
||||
props.raw.get(horizon_client::lod_net::PROPERTY),
|
||||
),
|
||||
|
||||
@@ -979,6 +979,24 @@ fn login_and_configure(
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
// An older client's packet ids are translated, not yet what is inside them (#10404): the registry data,
|
||||
// tags and play packets would reach it in 26.3's ids and layouts, so it is turned away before them.
|
||||
if let Some(version) = c.older_client()
|
||||
&& !shared.config.older_clients_play
|
||||
{
|
||||
let text = super::OLDER_CLIENTS_UNFINISHED;
|
||||
let reason = horizon_world::nbt::Tag::String(horizon_world::nbt::JavaString::from(text));
|
||||
let _ = send(c, cfg::Clientbound::Disconnect(cfg::Disconnect { reason }));
|
||||
if let Some(log) = &shared.config.log {
|
||||
let line = format!(
|
||||
"Disconnecting {name}: its client is on protocol {} ({text})",
|
||||
version.protocol()
|
||||
);
|
||||
(log.write)("Server thread", "INFO", &line);
|
||||
}
|
||||
lost(shared, &format!("{name} ({})", uuid_string(uuid)), text);
|
||||
return Ok(None);
|
||||
}
|
||||
// Custom component IDs need Horizon's registry check. The server may also
|
||||
// require Horizon clients even when it has no custom components.
|
||||
if brand != "horizon"
|
||||
|
||||
@@ -153,6 +153,9 @@ pub const HORIZON_CLIENTS_ONLY_REASON: &str =
|
||||
/// The server.properties key (server.toml's `server.accept_older_clients`) that lets 26.2 clients join
|
||||
/// (off unless "true").
|
||||
pub const ACCEPT_OLDER_CLIENTS: &str = "horizon-accept-older-clients";
|
||||
/// What a 26.2 client is told while only its packet ids are translated (#10404).
|
||||
pub const OLDER_CLIENTS_UNFINISHED: &str =
|
||||
"This server lets 26.2 join, but Horizon cannot translate 26.2 yet: join with 26.3";
|
||||
|
||||
/// How the server side of the network behaves: an integrated server's LAN
|
||||
/// uses the defaults, a dedicated server takes them from server.properties.
|
||||
@@ -216,6 +219,12 @@ pub struct NetConfig {
|
||||
/// `horizon-accept-older-clients` (off): a client of 26.2 joins too, its packets renumbered
|
||||
/// (horizon_protocol::adapter::ClientSession). Off, it is turned away as vanilla turns it away.
|
||||
pub accept_older_clients: bool,
|
||||
/// Lets an older client past configuration although only its packet ids are translated yet
|
||||
/// (#10404's later parts translate the registry ids and the packets whose layout changed): for tests
|
||||
/// of the id renumbering, never in server.toml. False, such a client is told
|
||||
/// [`OLDER_CLIENTS_UNFINISHED`] before any registry data, tag or play packet would reach it in 26.3's
|
||||
/// ids and layouts.
|
||||
pub older_clients_play: bool,
|
||||
/// `horizon-lod-distance` (Horizon's own server.properties key, 64): how
|
||||
/// far, in chunks, the server samples far terrain for Horizon clients
|
||||
/// (`lod_net`); 0 serves none.
|
||||
@@ -265,6 +274,7 @@ impl NetConfig {
|
||||
server_links: Vec::new(),
|
||||
horizon_clients_only: false,
|
||||
accept_older_clients: false,
|
||||
older_clients_play: false,
|
||||
lod_distance: crate::lod_net::DEFAULT_DISTANCE,
|
||||
levels: crate::chunk::LevelShapes::VANILLA,
|
||||
owner_profile: None,
|
||||
|
||||
@@ -1786,8 +1786,8 @@ impl Numbered {
|
||||
}
|
||||
}
|
||||
|
||||
/// A login as `version`: `Err` with the refusal's JSON, or the client once in play, with the ids of the item
|
||||
/// tag `beacon_payment_items` it was sent.
|
||||
/// A login as `version`: `Err` with the refusal (login's JSON, configuration's text), or the client once in
|
||||
/// play, with the ids of the item tag `beacon_payment_items` it was sent.
|
||||
fn join_as(
|
||||
version: ServerVersion,
|
||||
port: u16,
|
||||
@@ -1856,6 +1856,12 @@ fn join_as(
|
||||
}
|
||||
}
|
||||
}
|
||||
cfg::Clientbound::Disconnect(d) => {
|
||||
return Ok(Err(match d.reason {
|
||||
horizon_world::nbt::Tag::String(s) => s.to_string_lossy(),
|
||||
other => format!("{other:?}"),
|
||||
}));
|
||||
}
|
||||
cfg::Clientbound::FinishConfiguration(_) => {
|
||||
bot.send(cfg::Serverbound::FinishConfiguration(Empty).encode())?;
|
||||
bot.state = State::Play;
|
||||
@@ -1895,8 +1901,10 @@ fn status_protocol(port: u16, protocol: i32) -> Result<i64, String> {
|
||||
}
|
||||
|
||||
/// `accept_older_clients` off (the default), a 26.2 client is turned away as vanilla turns it away and the
|
||||
/// server list shows 26.3's protocol; on, the client is told its own protocol, logs in, plays in 26.2's
|
||||
/// packet ids and sees the block it places, while a 26.3 client is served as before.
|
||||
/// server list shows 26.3's protocol; on, the client is told its own protocol, logs in in 26.2's packet ids
|
||||
/// and is told, before any registry data, that 26.2 isn't translated yet, while a 26.3 client is served as
|
||||
/// before. Let past that (the tests-only `older_clients_play`), it plays in 26.2's packet ids and sees the
|
||||
/// block it places.
|
||||
#[test]
|
||||
fn a_26_2_client_joins_only_a_server_that_accepts_older_clients() {
|
||||
let Some(resources) = resources() else {
|
||||
@@ -1932,7 +1940,31 @@ fn a_26_2_client_joins_only_a_server_that_accepts_older_clients() {
|
||||
))
|
||||
});
|
||||
assert_eq!(statuses, (776, i64::from(PROTOCOL_VERSION)));
|
||||
let placed = run(&mut on, move || {
|
||||
let unfinished = run(&mut on, move || {
|
||||
join_as(OLD, port, "Old")?
|
||||
.err()
|
||||
.ok_or_else(|| "a 26.2 client got past configuration".to_owned())
|
||||
});
|
||||
assert!(
|
||||
unfinished.contains(horizon_client::lan::OLDER_CLIENTS_UNFINISHED),
|
||||
"{unfinished}"
|
||||
);
|
||||
// A 26.3 client on the same server: its own ids, as on any server.
|
||||
let payment = run(&mut on, move || {
|
||||
let (_bot, payment) =
|
||||
join_as(ServerVersion::Current, port, "New")?.map_err(|r| format!("refused: {r}"))?;
|
||||
Ok(payment)
|
||||
});
|
||||
assert!(payment.contains(&DIAMOND_26_3), "{payment:?}");
|
||||
|
||||
let net = NetConfig {
|
||||
accept_older_clients: true,
|
||||
older_clients_play: true,
|
||||
..NetConfig::loopback(0, "Test - World".into(), 8)
|
||||
};
|
||||
let mut play = server_with(&resources, net);
|
||||
let port = play.hub.lan.port;
|
||||
let placed = run(&mut play, move || {
|
||||
let (mut bot, _) = join_as(OLD, port, "Old")?.map_err(|r| format!("refused: {r}"))?;
|
||||
let (mut login, mut chunks, mut placed) = (false, 0, None);
|
||||
let deadline = Instant::now() + Duration::from_secs(15);
|
||||
@@ -2000,13 +2032,5 @@ fn a_26_2_client_joins_only_a_server_that_accepts_older_clients() {
|
||||
Ok(placed.flatten())
|
||||
});
|
||||
assert_eq!(placed, Some(([9, MIN_Y + 5, 9], STONE as i32)));
|
||||
assert_eq!(on.placed.len(), 1);
|
||||
|
||||
// A 26.3 client on the same server: its own ids, as on any server.
|
||||
let payment = run(&mut on, move || {
|
||||
let (_bot, payment) =
|
||||
join_as(ServerVersion::Current, port, "New")?.map_err(|r| format!("refused: {r}"))?;
|
||||
Ok(payment)
|
||||
});
|
||||
assert!(payment.contains(&DIAMOND_26_3), "{payment:?}");
|
||||
assert_eq!(play.placed.len(), 1);
|
||||
}
|
||||
@@ -393,6 +393,10 @@ impl Connection {
|
||||
self.reader.older_client = session.clone();
|
||||
self.writer.older_client = session;
|
||||
}
|
||||
/// The older release this connection's client is on ([`Self::serve_older`]), `None` for the pinned one.
|
||||
pub fn older_client(&self) -> Option<crate::adapter::ServerVersion> {
|
||||
self.reader.older_client.as_ref().map(|s| s.version)
|
||||
}
|
||||
/// `Connection.isMemoryConnection`: through a [`Pipe`] rather than a socket.
|
||||
pub fn is_memory(&self) -> bool {
|
||||
matches!(self.reader.stream, Stream::Memory(_))
|
||||
|
||||
+3
-2
@@ -214,8 +214,9 @@ any other release. With `accept_older_clients = true` a 26.2 client may join too
|
||||
ways, with no proxy in between, and the server list shows the server as joinable for 26.2.
|
||||
|
||||
This is work in progress (#10404): the ids inside packets and the packets whose layout changed between 26.2 and
|
||||
26.3 aren't translated yet, so a 26.2 player can't play on the server yet. 26.3 clients are served exactly as
|
||||
before, whatever the setting.
|
||||
26.3 aren't translated yet, so for now a 26.2 player gets through the login and is then told that 26.2 can't be
|
||||
translated yet, before the server sends anything it would misread. 26.3 clients are served exactly as before,
|
||||
whatever the setting.
|
||||
|
||||
### Seeing further: `max_view_distance`
|
||||
|
||||
|
||||
Reference in new issue
Block a user