chore: more plugin api work
also added bedrock fuzzing
This commit is contained in:
parent
7bf248d16a
commit
c15ebf181d
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -2735,7 +2735,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ureq",
|
||||
"uuid",
|
||||
"wasmparser 0.245.1",
|
||||
"wasmparser 0.246.1",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
]
|
||||
@ -4352,6 +4352,19 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.246.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d991c35d79bf8336dc1cd632ed4aacf0dc5fac4bc466c670625b037b972bb9c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"semver",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmprinter"
|
||||
version = "0.245.1"
|
||||
|
||||
@ -181,7 +181,7 @@ ureq = "3.3.0"
|
||||
wasmtime = "43.0"
|
||||
wasmtime-wasi = "43.0"
|
||||
wit-bindgen = "0.54"
|
||||
wasmparser = "0.245"
|
||||
wasmparser = "0.246"
|
||||
|
||||
postcard = { version = "1.1", features = ["alloc"] }
|
||||
tracing-serde-structured = "0.4"
|
||||
|
||||
@ -4,6 +4,16 @@ interface player {
|
||||
use permission.{permission-level};
|
||||
use text.{text-component};
|
||||
|
||||
record player-abilities {
|
||||
invulnerable: bool,
|
||||
flying: bool,
|
||||
allow-flying: bool,
|
||||
creative: bool,
|
||||
allow-modify-world: bool,
|
||||
fly-speed: f32,
|
||||
walk-speed: f32,
|
||||
}
|
||||
|
||||
resource player {
|
||||
get-id: func () -> result<string, string>;
|
||||
get-name: func () -> result<string, string>;
|
||||
@ -45,8 +55,23 @@ interface player {
|
||||
get-max-health: func () -> result<f32, string>;
|
||||
get-food-level: func () -> result<u8, string>;
|
||||
get-saturation: func () -> result<f32, string>;
|
||||
get-experience-level: func () -> result<s32, string>;
|
||||
get-experience-progress: func () -> result<f32, string>;
|
||||
get-experience-points: func () -> result<s32, string>;
|
||||
|
||||
get-experience-level: func() -> result<s32, string>;
|
||||
get-experience-progress: func() -> result<f32, string>;
|
||||
get-experience-points: func() -> result<s32, string>;
|
||||
set-experience-level: func(level: s32) -> result<_, string>;
|
||||
set-experience-progress: func(progress: f32) -> result<_, string>;
|
||||
set-experience-points: func(points: s32) -> result<_, string>;
|
||||
add-experience-levels: func(levels: s32) -> result<_, string>;
|
||||
add-experience-points: func(points: s32) -> result<_, string>;
|
||||
|
||||
is-sneaking: func() -> result<bool, string>;
|
||||
is-sprinting: func() -> result<bool, string>;
|
||||
is-on-ground: func() -> result<bool, string>;
|
||||
is-flying: func() -> result<bool, string>;
|
||||
set-flying: func(flying: bool) -> result<_, string>;
|
||||
|
||||
get-abilities: func() -> result<player-abilities, string>;
|
||||
set-abilities: func(abilities: player-abilities) -> result<_, string>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,16 @@ interface server {
|
||||
}
|
||||
|
||||
resource server {
|
||||
/// Returns the current difficulty level of the server
|
||||
get-difficulty: func() -> difficulty;
|
||||
|
||||
/// Returns the total number of players connected across all worlds.
|
||||
get-player-count: func() -> u32;
|
||||
|
||||
/// Returns the rolling average Milliseconds Per Tick (MSPT) over the last 100 ticks
|
||||
get-mspt: func() -> f64;
|
||||
|
||||
/// Returns the effective Ticks Per Second (TPS)
|
||||
get-tps: func() -> f64;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,49 @@
|
||||
interface %world {
|
||||
resource %world {
|
||||
get-id: func () -> string;
|
||||
enum piston-behavior {
|
||||
normal,
|
||||
destroy,
|
||||
block,
|
||||
ignore,
|
||||
push-only
|
||||
}
|
||||
}
|
||||
|
||||
flags block-flags {
|
||||
notify-neighbors,
|
||||
notify-listeners,
|
||||
force-state,
|
||||
skip-drops,
|
||||
moved,
|
||||
skip-redstone-wire-state-replacement,
|
||||
skip-block-entity-replaced-callback,
|
||||
skip-block-added-callback,
|
||||
}
|
||||
|
||||
record block-state {
|
||||
id: u16,
|
||||
luminance: u8,
|
||||
opacity: u8,
|
||||
hardness: f32,
|
||||
is-air: bool,
|
||||
is-liquid: bool,
|
||||
is-solid: bool,
|
||||
is-full-cube: bool,
|
||||
piston-behavior: piston-behavior,
|
||||
has-random-ticks: bool,
|
||||
}
|
||||
|
||||
record block-pos {
|
||||
x: s32,
|
||||
y: s32,
|
||||
z: s32,
|
||||
}
|
||||
|
||||
resource %world {
|
||||
get-id: func() -> string;
|
||||
|
||||
get-block-state-id: func(pos: block-pos) -> u16;
|
||||
|
||||
get-block-state: func(pos: block-pos) -> block-state;
|
||||
|
||||
set-block-state: func(pos: block-pos, state: u16, update-flags: block-flags);
|
||||
}
|
||||
}
|
||||
80
pumpkin-protocol/fuzz/Cargo.lock
generated
80
pumpkin-protocol/fuzz/Cargo.lock
generated
@ -33,9 +33,9 @@ checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.40"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d67d43201f4d20c78bcda740c142ca52482d81da80681533d33bf3f0596c8e2"
|
||||
checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
@ -289,9 +289,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossfire"
|
||||
version = "3.1.3"
|
||||
version = "3.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4150133c3f558d3190b52a4d36b571e29c18a24e98d24d12201bed0c038986f8"
|
||||
checksum = "e0042e53977a94d5d10de04ce7eb6016aa212c08e83ea202f7a9f102ff104303"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"futures-core",
|
||||
@ -1039,9 +1039,19 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-codecs"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"either",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-config"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"pumpkin-util",
|
||||
"serde",
|
||||
@ -1052,18 +1062,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-data"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"crc-fast",
|
||||
"phf",
|
||||
"pumpkin-nbt",
|
||||
"pumpkin-util",
|
||||
"rand",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-macros"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"proc-macro-error2",
|
||||
"pumpkin-data",
|
||||
@ -1073,18 +1084,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-nbt"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cesu8",
|
||||
"flate2",
|
||||
"pumpkin-codecs",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-protocol"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"async-compression",
|
||||
@ -1110,12 +1123,13 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"libfuzzer-sys",
|
||||
"pumpkin-protocol",
|
||||
"pumpkin-util",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-util"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
@ -1139,7 +1153,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pumpkin-world"
|
||||
version = "0.1.0-dev+1.21.11"
|
||||
version = "0.1.0-dev+26.1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
@ -1160,7 +1174,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"ruzstd",
|
||||
"serde",
|
||||
"sha2 0.11.0-rc.5",
|
||||
"sha2 0.11.0",
|
||||
"slotmap",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@ -1231,9 +1245,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
@ -1321,9 +1335,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.4"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@ -1341,12 +1355,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0-rc.5"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024"
|
||||
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.0",
|
||||
]
|
||||
|
||||
@ -1486,9 +1500,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.49.0"
|
||||
version = "1.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
||||
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@ -1528,9 +1542,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.0.3+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c"
|
||||
checksum = "994b95d9e7bae62b34bab0e2a4510b801fa466066a6a8b2b57361fa1eba068ee"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
@ -1543,27 +1557,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.0.0+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e"
|
||||
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4"
|
||||
checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.6+spec-1.1.0"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
|
||||
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@ -1632,9 +1646,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.21.0"
|
||||
version = "1.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb"
|
||||
checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
|
||||
dependencies = [
|
||||
"getrandom 0.4.1",
|
||||
"js-sys",
|
||||
@ -1843,9 +1857,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.14"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
|
||||
@ -19,9 +19,19 @@ libfuzzer-sys = "0.4"
|
||||
[dependencies.pumpkin-protocol]
|
||||
path = ".."
|
||||
|
||||
[dependencies.pumpkin-util]
|
||||
path = "../../pumpkin-util"
|
||||
|
||||
[[bin]]
|
||||
name = "decoder_java"
|
||||
path = "fuzz_targets/decoder_java.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "decoder_bedrock"
|
||||
path = "fuzz_targets/decoder_bedrock.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
97
pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs
Normal file
97
pumpkin-protocol/fuzz/fuzz_targets/decoder_bedrock.rs
Normal file
@ -0,0 +1,97 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_protocol::bedrock::packet_decoder::UDPNetworkDecoder;
|
||||
use pumpkin_protocol::bedrock::server::{
|
||||
client_cache_status::SClientCacheStatus,
|
||||
command_request::SCommandRequest,
|
||||
container_close::SContainerClose,
|
||||
interaction::SInteraction,
|
||||
loading_screen::SLoadingScreen,
|
||||
login::SLogin,
|
||||
player_auth_input::SPlayerAuthInput,
|
||||
raknet::{
|
||||
connection::SConnectionRequest,
|
||||
open_connection::{SOpenConnectionRequest1, SOpenConnectionRequest2},
|
||||
unconnected_ping::SUnconnectedPing,
|
||||
},
|
||||
request_chunk_radius::SRequestChunkRadius,
|
||||
request_network_settings::SRequestNetworkSettings,
|
||||
text::SText,
|
||||
};
|
||||
use pumpkin_protocol::serial::PacketRead;
|
||||
use std::io::Cursor;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: Run every Serverbound packet's read method.
|
||||
// ---------------------------------------------------------------------------
|
||||
fn fuzz_serverbound_packets(payload: &[u8]) {
|
||||
let mut cursor = Cursor::new(payload);
|
||||
|
||||
macro_rules! run_read {
|
||||
($($packet:ty),* $(,)?) => {
|
||||
$(
|
||||
cursor.set_position(0);
|
||||
// Attempt to read as a standard Game Packet (with version)
|
||||
let _ = <$packet>::read(&mut cursor);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Standard Bedrock Serverbound Packets
|
||||
run_read!(
|
||||
SClientCacheStatus,
|
||||
SCommandRequest,
|
||||
SContainerClose,
|
||||
SInteraction,
|
||||
SLoadingScreen,
|
||||
SLogin,
|
||||
SPlayerAuthInput,
|
||||
SRequestChunkRadius,
|
||||
SRequestNetworkSettings,
|
||||
SText,
|
||||
);
|
||||
|
||||
// RakNet Handshake Packets (Usually read without version)
|
||||
cursor.set_position(0);
|
||||
let _ = SConnectionRequest::read(&mut cursor);
|
||||
cursor.set_position(0);
|
||||
let _ = SOpenConnectionRequest1::read(&mut cursor);
|
||||
cursor.set_position(0);
|
||||
let _ = SOpenConnectionRequest2::read(&mut cursor);
|
||||
cursor.set_position(0);
|
||||
let _ = SUnconnectedPing::read(&mut cursor);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fuzz Target
|
||||
// ---------------------------------------------------------------------------
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if data.len() < 20 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split data for decoder configuration vs raw payload
|
||||
let threshold_raw = data[0];
|
||||
let key: [u8; 16] = data[1..17].try_into().unwrap();
|
||||
let stream_data = &data[17..];
|
||||
|
||||
let mut decoder = UDPNetworkDecoder::new();
|
||||
|
||||
// Setup Decoder
|
||||
if threshold_raw > 0 {
|
||||
// Assuming your CompressionThreshold is a wrapper around u32
|
||||
decoder.set_compression((threshold_raw as u32).try_into().unwrap());
|
||||
}
|
||||
decoder.set_encryption(&key);
|
||||
|
||||
// 1. Fuzz the Decoder (Framing/VarInts/Bitmasks)
|
||||
let decoder_cursor = Cursor::new(stream_data.to_vec());
|
||||
if let Ok(raw_packet) = decoder.get_game_packet(decoder_cursor) {
|
||||
// If framed correctly, fuzz the internal payload
|
||||
fuzz_serverbound_packets(&raw_packet.payload);
|
||||
}
|
||||
|
||||
// 2. Shotgun Fuzz
|
||||
// Pass raw data directly to all readers to catch panics in the parsers
|
||||
fuzz_serverbound_packets(stream_data);
|
||||
});
|
||||
@ -1,96 +1,102 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use pumpkin_protocol::{
|
||||
ServerPacket,
|
||||
java::{
|
||||
packet_decoder::TCPNetworkDecoder,
|
||||
server::{
|
||||
config::{
|
||||
SClientInformationConfig, SConfigCookieResponse, SConfigResourcePack, SKnownPacks,
|
||||
SPluginMessage,
|
||||
},
|
||||
handshake::SHandShake,
|
||||
login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart},
|
||||
play::{
|
||||
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot,
|
||||
SClientCommand, SClientInformationPlay, SCloseContainer, SCommandSuggestion,
|
||||
SConfirmTeleport, SCookieResponse, SCustomPayload, SInteract, SKeepAlive,
|
||||
SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities,
|
||||
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
|
||||
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SSetCommandBlock,
|
||||
SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem,
|
||||
SUseItemOn,
|
||||
},
|
||||
status::SStatusPingRequest,
|
||||
use pumpkin_protocol::ServerPacket;
|
||||
use pumpkin_protocol::java::{
|
||||
packet_decoder::TCPNetworkDecoder,
|
||||
server::{
|
||||
config::{
|
||||
SClientInformationConfig, SConfigCookieResponse, SConfigResourcePack, SKnownPacks,
|
||||
SPluginMessage,
|
||||
},
|
||||
handshake::SHandShake,
|
||||
login::{SEncryptionResponse, SLoginCookieResponse, SLoginPluginResponse, SLoginStart},
|
||||
play::{
|
||||
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
|
||||
SClientInformationPlay, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
|
||||
SCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle, SPaddleBoat,
|
||||
SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPlayerCommand,
|
||||
SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation,
|
||||
SPlayerSession, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround,
|
||||
SSwingArm, SUpdateSign, SUseItem, SUseItemOn,
|
||||
},
|
||||
status::SStatusPingRequest,
|
||||
},
|
||||
};
|
||||
use pumpkin_util::version::MinecraftVersion;
|
||||
use std::io::Cursor;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
const TARGET_VERSION: MinecraftVersion = MinecraftVersion::V_26_1;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: run every known ServerPacket::read against the same payload.
|
||||
// We don't care about the result — only that nothing panics.
|
||||
// Uses a Cursor and the Version enum as required by the new signature.
|
||||
// ---------------------------------------------------------------------------
|
||||
fn fuzz_all_deserializers(payload: &[u8]) {
|
||||
// Handshake
|
||||
let _ = SHandShake::read(payload);
|
||||
let mut cursor = Cursor::new(payload);
|
||||
|
||||
// Status
|
||||
//let _ = SStatusRequest::read(payload);
|
||||
let _ = SStatusPingRequest::read(payload);
|
||||
macro_rules! run_read {
|
||||
($($packet:ty),* $(,)?) => {
|
||||
$(
|
||||
cursor.set_position(0);
|
||||
let _ = <$packet>::read(&mut cursor, &TARGET_VERSION);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// Login
|
||||
let _ = SLoginStart::read(payload);
|
||||
let _ = SEncryptionResponse::read(payload);
|
||||
let _ = SLoginPluginResponse::read(payload);
|
||||
//let _ = SLoginAcknowledged::read(payload);
|
||||
let _ = SLoginCookieResponse::read(payload);
|
||||
|
||||
// Config
|
||||
let _ = SClientInformationConfig::read(payload);
|
||||
let _ = SPluginMessage::read(payload);
|
||||
//let _ = SAcknowledgeFinishConfig::read(payload);
|
||||
let _ = SKnownPacks::read(payload);
|
||||
let _ = SConfigCookieResponse::read(payload);
|
||||
let _ = SConfigResourcePack::read(payload);
|
||||
|
||||
// Play
|
||||
let _ = SConfirmTeleport::read(payload);
|
||||
let _ = SChangeGameMode::read(payload);
|
||||
let _ = SChatCommand::read(payload);
|
||||
let _ = SChatMessage::read(payload);
|
||||
let _ = SClientInformationPlay::read(payload);
|
||||
let _ = SClientCommand::read(payload);
|
||||
let _ = SPlayerInput::read(payload);
|
||||
let _ = SMoveVehicle::read(payload);
|
||||
let _ = SPaddleBoat::read(payload);
|
||||
let _ = SInteract::read(payload);
|
||||
let _ = SKeepAlive::read(payload);
|
||||
let _ = SPlayerPosition::read(payload);
|
||||
let _ = SPlayerPositionRotation::read(payload);
|
||||
let _ = SPlayerRotation::read(payload);
|
||||
let _ = SSetPlayerGround::read(payload);
|
||||
let _ = SPickItemFromBlock::read(payload);
|
||||
let _ = SPlayerAbilities::read(payload);
|
||||
let _ = SPlayerAction::read(payload);
|
||||
let _ = SSetCommandBlock::read(payload);
|
||||
let _ = SPlayerCommand::read(payload);
|
||||
let _ = SPlayerLoaded::read(payload);
|
||||
let _ = SPlayPingRequest::read(payload);
|
||||
let _ = SClickSlot::read(payload);
|
||||
let _ = SSetHeldItem::read(payload);
|
||||
let _ = SSetCreativeSlot::read(payload);
|
||||
let _ = SSwingArm::read(payload);
|
||||
let _ = SUpdateSign::read(payload);
|
||||
let _ = SUseItemOn::read(payload);
|
||||
let _ = SUseItem::read(payload);
|
||||
let _ = SCommandSuggestion::read(payload);
|
||||
let _ = SCookieResponse::read(payload);
|
||||
let _ = SCloseContainer::read(payload);
|
||||
let _ = SChunkBatch::read(payload);
|
||||
let _ = SPlayerSession::read(payload);
|
||||
let _ = SCustomPayload::read(payload);
|
||||
run_read!(
|
||||
// Handshake
|
||||
SHandShake,
|
||||
// Status
|
||||
SStatusPingRequest,
|
||||
// Login
|
||||
SLoginStart,
|
||||
SEncryptionResponse,
|
||||
SLoginPluginResponse,
|
||||
SLoginCookieResponse,
|
||||
// Config
|
||||
SClientInformationConfig,
|
||||
SPluginMessage,
|
||||
SKnownPacks,
|
||||
SConfigCookieResponse,
|
||||
SConfigResourcePack,
|
||||
// Play
|
||||
SConfirmTeleport,
|
||||
SChangeGameMode,
|
||||
SChatCommand,
|
||||
SChatMessage,
|
||||
SClientInformationPlay,
|
||||
SClientCommand,
|
||||
SPlayerInput,
|
||||
SMoveVehicle,
|
||||
SPaddleBoat,
|
||||
SInteract,
|
||||
SKeepAlive,
|
||||
SPlayerPosition,
|
||||
SPlayerPositionRotation,
|
||||
SPlayerRotation,
|
||||
SSetPlayerGround,
|
||||
SPickItemFromBlock,
|
||||
SPlayerAbilities,
|
||||
SPlayerAction,
|
||||
SSetCommandBlock,
|
||||
SPlayerCommand,
|
||||
SPlayerLoaded,
|
||||
SPlayPingRequest,
|
||||
SClickSlot,
|
||||
SSetHeldItem,
|
||||
SSetCreativeSlot,
|
||||
SSwingArm,
|
||||
SUpdateSign,
|
||||
SUseItemOn,
|
||||
SUseItem,
|
||||
SCommandSuggestion,
|
||||
SCookieResponse,
|
||||
SCloseContainer,
|
||||
SChunkBatch,
|
||||
SPlayerSession,
|
||||
SCustomPayload,
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -101,13 +107,6 @@ fuzz_target!(|data: &[u8]| {
|
||||
return;
|
||||
}
|
||||
|
||||
// Byte layout:
|
||||
// [0] = decoder mode (0=plain, 1=compressed, 2=encrypted, 3=both)
|
||||
// [1] = split point % rest.len() — how much goes to the decoder vs
|
||||
// the deserializers, letting the fuzzer explore both paths
|
||||
// independently with different byte patterns
|
||||
// [2..18] = 16-byte AES key candidate
|
||||
// [18..] = raw bytes fed to both fuzzing paths
|
||||
let mode = data[0] % 4;
|
||||
let key = &data[2..18];
|
||||
let rest = &data[18..];
|
||||
@ -143,5 +142,6 @@ fuzz_target!(|data: &[u8]| {
|
||||
let _ = decoder.get_raw_packet().await;
|
||||
});
|
||||
|
||||
// --- Path 2: Individual Packet Deserializers ---------------------------
|
||||
fuzz_all_deserializers(deser_bytes);
|
||||
});
|
||||
|
||||
@ -9,7 +9,8 @@ use bytes::Bytes;
|
||||
use tokio::io::{AsyncRead, BufReader, ReadBuf};
|
||||
|
||||
use crate::{
|
||||
Aes128Cfb8Dec, CompressionThreshold, PacketDecodeError, RawPacket, StreamDecryptor,
|
||||
Aes128Cfb8Dec, CompressionThreshold, MAX_PACKET_SIZE, PacketDecodeError, RawPacket,
|
||||
StreamDecryptor,
|
||||
codec::var_uint::VarUInt,
|
||||
ser::{NetworkReadExt, ReadingError},
|
||||
};
|
||||
@ -138,6 +139,9 @@ impl UDPNetworkDecoder {
|
||||
})?;
|
||||
|
||||
let packet_len = u64::from(packet_len.0);
|
||||
if !(0..=MAX_PACKET_SIZE).contains(&packet_len) {
|
||||
Err(PacketDecodeError::OutOfBounds)?;
|
||||
}
|
||||
|
||||
let var_header = VarUInt::decode(&mut reader)?;
|
||||
|
||||
@ -159,8 +163,17 @@ impl UDPNetworkDecoder {
|
||||
// Gamepacket ID: Remaining 10 bits (bits 4 to 13)
|
||||
let gamepacket_id = (header & 0x3FF) as u16; // 0x3FF is 10 bits set to 1
|
||||
|
||||
let header_size = var_header.written_size();
|
||||
|
||||
if (packet_len as usize) < header_size {
|
||||
return Err(PacketDecodeError::MalformedLength(
|
||||
"Packet length is smaller than the header size".to_string(),
|
||||
));
|
||||
}
|
||||
let payload_len = packet_len as usize - header_size;
|
||||
|
||||
let payload = reader
|
||||
.read_boxed_slice(packet_len as usize - var_header.written_size())
|
||||
.read_boxed_slice(payload_len)
|
||||
.map_err(|err| PacketDecodeError::FailedDecompression(err.to_string()))?;
|
||||
|
||||
Ok(RawPacket {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use pumpkin_macros::packet;
|
||||
use std::io::{Error, Read};
|
||||
use std::io::{Error, ErrorKind, Read};
|
||||
|
||||
use crate::{codec::var_uint::VarUInt, serial::PacketRead};
|
||||
|
||||
@ -15,15 +15,30 @@ pub struct SLogin {
|
||||
|
||||
impl PacketRead for SLogin {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
const MAX_TOKEN_SIZE: usize = 256 * 1024; // 256KB limit
|
||||
|
||||
let protocol_version = i32::read_be(reader)?;
|
||||
|
||||
let _len = VarUInt::read(reader)?;
|
||||
|
||||
let jwt_len = u32::read(reader)?;
|
||||
let mut jwt = vec![0; jwt_len as _];
|
||||
let jwt_len = u32::read_be(reader)? as usize;
|
||||
if jwt_len > MAX_TOKEN_SIZE {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"JWT length exceeds limit",
|
||||
));
|
||||
}
|
||||
let mut jwt = vec![0u8; jwt_len];
|
||||
reader.read_exact(&mut jwt)?;
|
||||
|
||||
let raw_token_len = u32::read(reader)?;
|
||||
let mut raw_token = vec![0; raw_token_len as _];
|
||||
let raw_token_len = u32::read_be(reader)? as usize;
|
||||
if raw_token_len > MAX_TOKEN_SIZE {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
"Raw token length exceeds limit",
|
||||
));
|
||||
}
|
||||
let mut raw_token = vec![0u8; raw_token_len];
|
||||
reader.read_exact(&mut raw_token)?;
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{Error, Read},
|
||||
io::{Error, ErrorKind, Read},
|
||||
net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
};
|
||||
|
||||
@ -133,8 +133,22 @@ impl<T: PacketRead, const N: usize> PacketRead for [T; N] {
|
||||
|
||||
impl PacketRead for String {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let vec = Vec::read(reader)?;
|
||||
Ok(unsafe { Self::from_utf8_unchecked(vec) })
|
||||
const MAX_STRING_LENGTH: usize = 32767;
|
||||
|
||||
let len = u32::read(reader)? as usize;
|
||||
|
||||
if len > MAX_STRING_LENGTH {
|
||||
return Err(Error::new(
|
||||
ErrorKind::InvalidData,
|
||||
format!("String length {len} exceeds maximum of {MAX_STRING_LENGTH}"),
|
||||
));
|
||||
}
|
||||
|
||||
let mut buf = vec![0u8; len];
|
||||
reader.read_exact(&mut buf)?;
|
||||
|
||||
Self::from_utf8(buf)
|
||||
.map_err(|_| Error::new(ErrorKind::InvalidData, "Invalid UTF-8 sequence"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -401,6 +401,135 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
Ok(player.experience_points.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_experience_level(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
level: i32,
|
||||
) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player.set_experience_level(level, true).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_experience_progress(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
progress: f32,
|
||||
) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player
|
||||
.set_experience(
|
||||
player.experience_level.load(Ordering::Relaxed),
|
||||
progress,
|
||||
player.experience_points.load(Ordering::Relaxed),
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_experience_points(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
points: i32,
|
||||
) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player
|
||||
.set_experience(
|
||||
player.experience_level.load(Ordering::Relaxed),
|
||||
player.experience_progress.load(),
|
||||
points,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_experience_levels(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
levels: i32,
|
||||
) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player.add_experience_levels(levels).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_experience_points(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
points: i32,
|
||||
) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player.add_experience_points(points).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_sneaking(&mut self, player: Resource<Player>) -> Result<bool, String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().sneaking.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn is_sprinting(&mut self, player: Resource<Player>) -> Result<bool, String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().sprinting.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn is_on_ground(&mut self, player: Resource<Player>) -> Result<bool, String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.get_entity().on_ground.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn is_flying(&mut self, player: Resource<Player>) -> Result<bool, String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
Ok(player.is_flying().await)
|
||||
}
|
||||
|
||||
async fn set_flying(&mut self, player: Resource<Player>, flying: bool) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
{
|
||||
let mut abilities = player.abilities.lock().await;
|
||||
abilities.flying = flying;
|
||||
};
|
||||
player.send_abilities_update().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_abilities(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
) -> Result<pumpkin::plugin::player::PlayerAbilities, String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
let abilities = player.abilities.lock().await;
|
||||
Ok(pumpkin::plugin::player::PlayerAbilities {
|
||||
invulnerable: abilities.invulnerable,
|
||||
flying: abilities.flying,
|
||||
allow_flying: abilities.allow_flying,
|
||||
creative: abilities.creative,
|
||||
allow_modify_world: abilities.allow_modify_world,
|
||||
fly_speed: abilities.fly_speed,
|
||||
walk_speed: abilities.walk_speed,
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_abilities(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
abilities: pumpkin::plugin::player::PlayerAbilities,
|
||||
) -> Result<(), String> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
{
|
||||
let mut a = player.abilities.lock().await;
|
||||
a.invulnerable = abilities.invulnerable;
|
||||
a.flying = abilities.flying;
|
||||
a.allow_flying = abilities.allow_flying;
|
||||
a.creative = abilities.creative;
|
||||
a.allow_modify_world = abilities.allow_modify_world;
|
||||
a.fly_speed = abilities.fly_speed;
|
||||
a.walk_speed = abilities.walk_speed;
|
||||
};
|
||||
player.send_abilities_update().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<Player>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
|
||||
@ -33,4 +33,22 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
||||
pumpkin_util::Difficulty::Hard => Difficulty::Hard,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_player_count(
|
||||
&mut self,
|
||||
_server: Resource<pumpkin::plugin::server::Server>,
|
||||
) -> u32 {
|
||||
let server = self.server.as_ref().expect("server not available");
|
||||
server.get_player_count() as u32
|
||||
}
|
||||
|
||||
async fn get_mspt(&mut self, _server: Resource<pumpkin::plugin::server::Server>) -> f64 {
|
||||
let server = self.server.as_ref().expect("server not available");
|
||||
server.get_mspt()
|
||||
}
|
||||
|
||||
async fn get_tps(&mut self, _server: Resource<pumpkin::plugin::server::Server>) -> f64 {
|
||||
let server = self.server.as_ref().expect("server not available");
|
||||
server.get_tps()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
use pumpkin_data::block_state::PistonBehavior;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::BlockFlags;
|
||||
use wasmtime::component::Resource;
|
||||
|
||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::world::{
|
||||
BlockFlags as WitBlockFlags, BlockPos as WitBlockPos, BlockState as WitBlockState,
|
||||
PistonBehavior as WitPistonBehavior,
|
||||
};
|
||||
use crate::plugin::loader::wasm::wasm_host::{
|
||||
DowncastResourceExt,
|
||||
state::{PluginHostState, WorldResource},
|
||||
@ -44,6 +51,85 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
async fn get_block_state_id(&mut self, world: Resource<World>, pos: WitBlockPos) -> u16 {
|
||||
let world_ref = world.downcast_ref(self);
|
||||
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
|
||||
world_ref.provider.get_block_state_id(&internal_pos).await
|
||||
}
|
||||
|
||||
async fn get_block_state(&mut self, world: Resource<World>, pos: WitBlockPos) -> WitBlockState {
|
||||
let world_ref = world.downcast_ref(self);
|
||||
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
|
||||
// Fetch the actual BlockState struct from the world
|
||||
// get_block_state typically returns &'static BlockState in Pumpkin
|
||||
let state = world_ref.provider.get_block_state(&internal_pos).await;
|
||||
|
||||
WitBlockState {
|
||||
id: state.id,
|
||||
luminance: state.luminance,
|
||||
opacity: state.opacity,
|
||||
hardness: state.hardness,
|
||||
is_air: state.is_air(),
|
||||
is_liquid: state.is_liquid(),
|
||||
is_solid: state.is_solid(),
|
||||
is_full_cube: state.is_full_cube(),
|
||||
has_random_ticks: state.has_random_ticks(),
|
||||
piston_behavior: match state.piston_behavior {
|
||||
PistonBehavior::Normal => WitPistonBehavior::Normal,
|
||||
PistonBehavior::Destroy => WitPistonBehavior::Destroy,
|
||||
PistonBehavior::Block => WitPistonBehavior::Block,
|
||||
PistonBehavior::Ignore => WitPistonBehavior::Ignore,
|
||||
PistonBehavior::PushOnly => WitPistonBehavior::PushOnly,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_block_state(
|
||||
&mut self,
|
||||
world: Resource<World>,
|
||||
pos: WitBlockPos,
|
||||
state: u16,
|
||||
update_flags: WitBlockFlags,
|
||||
) {
|
||||
let world_ref = world.downcast_ref(self);
|
||||
let internal_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
|
||||
// Map WIT flags to your internal bitflags
|
||||
let mut internal_flags = BlockFlags::empty();
|
||||
if update_flags.contains(WitBlockFlags::NOTIFY_NEIGHBORS) {
|
||||
internal_flags |= BlockFlags::NOTIFY_NEIGHBORS;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::NOTIFY_LISTENERS) {
|
||||
internal_flags |= BlockFlags::NOTIFY_LISTENERS;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::FORCE_STATE) {
|
||||
internal_flags |= BlockFlags::FORCE_STATE;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::SKIP_DROPS) {
|
||||
internal_flags |= BlockFlags::SKIP_DROPS;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::MOVED) {
|
||||
internal_flags |= BlockFlags::MOVED;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::SKIP_REDSTONE_WIRE_STATE_REPLACEMENT) {
|
||||
internal_flags |= BlockFlags::SKIP_REDSTONE_WIRE_STATE_REPLACEMENT;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK) {
|
||||
internal_flags |= BlockFlags::SKIP_BLOCK_ENTITY_REPLACED_CALLBACK;
|
||||
}
|
||||
if update_flags.contains(WitBlockFlags::SKIP_BLOCK_ADDED_CALLBACK) {
|
||||
internal_flags |= BlockFlags::SKIP_BLOCK_ADDED_CALLBACK;
|
||||
}
|
||||
|
||||
// Update the world
|
||||
world_ref
|
||||
.provider
|
||||
.set_block_state(&internal_pos, state, internal_flags)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<World>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
|
||||
Loading…
Reference in New Issue
Block a user