Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed
- Stairs, fences, panes, bars, and tripwire could disappear for 26.50 clients on 26.40-26.45 servers. Existing blocks now load with the correct states, stair corners and connections follow neighboring blocks, and placing or removing blocks refreshes nearby shapes.

## [0.5.1] - 2026-09-15

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ minimum-version = "build-system.requires"
build-dir = "build/{wheel_tag}"
wheel.packages = ["src/endweave"]
wheel.py-api = "cp312"
wheel.exclude = ["**/*.cpp", "**/*.h", "**/*.pat", "**/CMakeLists.txt"]
wheel.exclude = ["**/*.cpp", "**/*.h", "**/*.inc", "**/*.pat", "**/CMakeLists.txt"]
install.components = ["python"]
sdist.exclude = [".github", "build", "tests"]

Expand All @@ -80,4 +80,4 @@ provider = "scikit_build_core.metadata.setuptools_scm"

[tool.setuptools_scm]
version_file = "src/endweave/_version.py"
local_scheme = "no-local-version"
local_scheme = "no-local-version"
30 changes: 28 additions & 2 deletions src/endweave/_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

#include <bedrock/protocol/network.h>
#include <nanobind/nanobind.h>
#include <nanobind/stl/function.h>
#include <nanobind/stl/optional.h>
#include <nanobind/stl/pair.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/vector.h>
#include <optional>
Expand Down Expand Up @@ -69,7 +71,8 @@ struct Translator {
int to_version = 0;
};

std::optional<nb::bytes> translate(const Translator &translator, int packet_id, nb::bytes payload)
std::optional<nb::bytes> translateImpl(const Translator &translator, int packet_id, nb::bytes payload,
endweave::blocks::WorldContext *world)
{
const endweave::PacketHandler handler = translator.engine.get(packet_id);
if (handler == nullptr) {
Expand All @@ -81,7 +84,7 @@ std::optional<nb::bytes> translate(const Translator &translator, int packet_id,
bp::BinaryWriter out{translated};
bp::BinaryReader in{std::string_view{payload.c_str(), payload.size()}};
bool cancelled = false;
const auto result = handler(cancelled, in, out);
const auto result = handler(cancelled, in, out, world);
if (!result) {
raiseTranslationError(packet_id, "translate", result.error());
}
Expand All @@ -91,6 +94,27 @@ std::optional<nb::bytes> translate(const Translator &translator, int packet_id,
return nb::bytes(translated.data(), translated.size());
}

std::optional<nb::bytes> translate(const Translator &translator, int packet_id, nb::bytes payload)
{
return translateImpl(translator, packet_id, payload, nullptr);
}

std::pair<std::optional<nb::bytes>, std::vector<nb::bytes>> translateWorld(const Translator &translator, int packet_id,
nb::bytes payload,
endweave::blocks::Lookup lookup,
int dimension)
{
endweave::blocks::WorldContext world{std::move(lookup), dimension, {}, {}};
auto result = translateImpl(translator, packet_id, payload, &world);
std::vector<nb::bytes> updates;
if (result) {
for (const auto &update : endweave::blocks::neighborUpdates(world)) {
updates.emplace_back(update.data(), update.size());
}
}
return {std::move(result), std::move(updates)};
}

} // namespace

NB_MODULE(_pipeline, m)
Expand Down Expand Up @@ -141,6 +165,8 @@ NB_MODULE(_pipeline, m)
return self.actions;
},
"The action for each packet id that needs one. Other ids pass through untouched.")
.def("translate_world", &translateWorld, "packet_id"_a, "payload"_a, "lookup"_a, "dimension"_a,
"Translate blocks using a dimension/x/y/z lookup; return payload and neighbor UpdateBlock payloads.")
.def("translate", &translate, "packet_id"_a, "payload"_a,
"The translated payload, or None if the packet was cancelled.");
}
7 changes: 6 additions & 1 deletion src/endweave/_pipeline.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Compile-time protocol translation between Bedrock versions."""

from collections.abc import Mapping
from collections.abc import Callable, Mapping
import enum


Expand Down Expand Up @@ -36,6 +36,11 @@ class Translator:
The action for each packet id that needs one. Other ids pass through untouched.
"""

def translate_world(self, packet_id: int, payload: bytes, lookup: Callable[[int, int, int, int], int], dimension: int) -> tuple[bytes | None, list[bytes]]:
"""
Translate blocks using a dimension/x/y/z lookup; return payload and neighbor UpdateBlock payloads.
"""

def translate(self, packet_id: int, payload: bytes) -> bytes | None:
"""The translated payload, or None if the packet was cancelled."""

Expand Down
1 change: 1 addition & 0 deletions src/endweave/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(self, address: str, server_protocol_version: ProtocolVersion) -> No
self.player: Player | None = None
self.active = True
self.pending_disconnect = False
self.sending_block_updates = False

@property
def id(self) -> int:
Expand Down
28 changes: 27 additions & 1 deletion src/endweave/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ def on_packet_receive(self, event: PacketReceiveEvent) -> None:
def on_packet_send(self, event: PacketSendEvent) -> None:
connection = self._connection_manager.get_connection(str(event.address))
if connection is not None:
if connection.sending_block_updates and event.packet_id == 21:
return
self._translate(connection, event, Direction.CLIENTBOUND)

def _translate(
Expand Down Expand Up @@ -200,8 +202,25 @@ def _translate(
if logged and debug.log_pre_packet_transform:
self.logger.info(f"[{direction.value}] {label} in: {event.payload.hex()}")

neighbor_updates: list[bytes] = []
try:
payload = translator.translate(packet_id, event.payload)
if (
direction is Direction.CLIENTBOUND
and translator.from_version < 2193 <= translator.to_version
and packet_id in (21, 58, 110, 172, 174)
and connection.player is not None
):
player_dimension = connection.player.dimension
dimensions = {dimension.type.value: dimension for dimension in player_dimension.level.dimensions}

def lookup_block(dimension_id: int, x: int, y: int, z: int) -> int:
return dimensions[dimension_id].get_block_at(x, y, z).data.runtime_id

payload, neighbor_updates = translator.translate_world(
packet_id, event.payload, lookup_block, player_dimension.type.value
)
else:
payload = translator.translate(packet_id, event.payload)
except TranslationError as error:
debug.log_translation_failure(
f"Failed to translate {direction.value} packet {_packet_label(packet_id)} "
Expand Down Expand Up @@ -230,6 +249,13 @@ def _translate(
self.logger.info(f"[{direction.value}] {label} out: {payload.hex()}")

event.payload = payload
if neighbor_updates and connection.player is not None:
connection.sending_block_updates = True
try:
for update in neighbor_updates:
connection.player.send_packet(21, update)
finally:
connection.sending_block_updates = False

@event_handler
def on_player_login(self, event: PlayerLoginEvent) -> None:
Expand Down
19 changes: 15 additions & 4 deletions src/endweave/protocol/handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "endweave/protocol/rewrite.h"
#include "endweave/protocol/transform.h"
#include "endweave/protocol/version.h"
#include "endweave/protocols/blocks.h"
#include "endweave/protocols/rewriters.h"
#include "endweave/protocols/v2168_to_v2193/transform.h"
#include "endweave/protocols/v2193_to_v2168/transform.h"
Expand Down Expand Up @@ -45,7 +46,8 @@ inline constexpr bool should_cancel_v =
template <int From, int To, int Id>
inline constexpr bool should_translate_v =
From != To && !(Id >= kSkipBegin && Id <= kSkipEnd) && path_has_packet_v<From, To, Id> &&
(Rewritable<From, To, Id> || !std::is_same_v<bp::packet_of_t<From, Id>, bp::packet_of_t<To, Id>>);
(Rewritable<From, To, Id> || blocks::needs_rewrite<From, To, Id> ||
!std::is_same_v<bp::packet_of_t<From, Id>, bp::packet_of_t<To, Id>>);

template <int Cur, int To, int Id>
void chain(const Context<bp::packet_of_t<To, Id>> &ctx, bp::packet_of_t<Cur, Id> &&from)
Expand All @@ -68,7 +70,8 @@ void chain(const Context<bp::packet_of_t<To, Id>> &ctx, bp::packet_of_t<Cur, Id>
}

template <int From, int To, int Id>
std::expected<void, std::error_code> handle(bool &cancelled, bp::BinaryReader &in, bp::BinaryWriter &out)
std::expected<void, std::error_code> handle(bool &cancelled, bp::BinaryReader &in, bp::BinaryWriter &out,
blocks::WorldContext *world)
{
auto result = bp::deserialize<bp::packet_of_t<From, Id>>(in);
if (!result) {
Expand All @@ -82,6 +85,12 @@ std::expected<void, std::error_code> handle(bool &cancelled, bp::BinaryReader &i
std::string translated;
bp::BinaryWriter writer{translated};
auto &&packet = std::move(result).value();
if constexpr (blocks::needs_rewrite<From, To, Id>) {
auto rewritten = blocks::rewrite<From, To, Id>(packet, world);
if (!rewritten) {
return std::unexpected(rewritten.error());
}
}
if constexpr (Rewritable<From, To, Id>) {
Rewriter<From, To, Id>::rewrite(packet);
}
Expand Down Expand Up @@ -114,7 +123,8 @@ std::expected<void, std::error_code> handle(bool &cancelled, bp::BinaryReader &i
}

/** @see ViaVersion PacketWrapper#cancel. */
inline std::expected<void, std::error_code> cancel(bool &cancelled, bp::BinaryReader &, bp::BinaryWriter &)
inline std::expected<void, std::error_code> cancel(bool &cancelled, bp::BinaryReader &, bp::BinaryWriter &,
blocks::WorldContext *)
{
cancelled = true;
return {};
Expand All @@ -130,7 +140,8 @@ enum class Action : char {
};

/** @see ViaVersion PacketHandler. */
using PacketHandler = std::expected<void, std::error_code> (*)(bool &, bp::BinaryReader &, bp::BinaryWriter &);
using PacketHandler = std::expected<void, std::error_code> (*)(bool &, bp::BinaryReader &, bp::BinaryWriter &,
blocks::WorldContext *);

namespace detail {

Expand Down
1 change: 1 addition & 0 deletions src/endweave/protocols/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target_sources(_pipeline PRIVATE blocks.cpp)
add_subdirectory(v2168_to_v2193)
add_subdirectory(v2193_to_v2168)
Loading