From beac4bdb42a4176a9ea76babdfdcc416eb801fc8 Mon Sep 17 00:00:00 2001 From: TriMill Date: Sun, 1 Jan 2023 15:28:35 -0500 Subject: [PATCH] update things --- docs/plugins.md | 40 +++++++++++++++------------- plugins/testcmd.lua | 4 +++ src/network/server.rs | 53 ++++++++++++++++++++++++++++++------- src/plugins/init.lua | 10 +++++++ src/plugins/mod.rs | 12 +++++++++ src/plugins/plugin.rs | 12 ++++++++- src/protocol/clientbound.rs | 4 +-- src/protocol/serverbound.rs | 32 ++++++++++++++++------ 8 files changed, 128 insertions(+), 39 deletions(-) diff --git a/docs/plugins.md b/docs/plugins.md index 5540e4a..c598748 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -6,30 +6,32 @@ Quectocraft plugins are written in Lua. Examples can be seen in the [plugins dir All information about a plugin is stored in a table, which must be returned at the end of the plugin. This table contains both information about the plugin and functions that act as event handlers. -| Field | Description | -|--------------------|---------------------------------------------------------------------------------------------------------| -| `id` | The plugin's ID. This should consist of lowercase letters and underscores only. | -| `name` | The plugin's human-readable name. | -| `description` | The plugin's description. | -| `authors` | A list of the plugin's authors. | -| `version` | The plugin's version (semantic versioning encouraged). | -| `init` | Called when the plugin is initialized. The `server` table is available at this point. | -| `registerCommands` | Called to register the plugin's commands. Arguments: a `registry` table. | -| `playerJoin` | Called when a player joins. Arguments: the player's name, the player's UUID. | -| `playerLeave` | Called when a player leaves. Arguments: the player's name, the player's UUID. | -| `chatMessage` | Called when a player sends a chat message. Arguments: the message, the player's name and UUID. | -| `command` | Called when a player runs a command. Arguments: the command, the arguments, the player's name and UUID. | +| Field | Description | +|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `id` | The plugin's ID. This should consist of lowercase letters and underscores only. | +| `name` | The plugin's human-readable name. | +| `description` | The plugin's description. | +| `authors` | A list of the plugin's authors. | +| `version` | The plugin's version (semantic versioning encouraged). | +| `init` | Called when the plugin is initialized. The `server` table is available at this point. | +| `registerCommands` | Called to register the plugin's commands. Arguments: a `registry` table. | +| `playerJoin` | Called when a player joins. Arguments: the player's name, the player's UUID. | +| `playerLeave` | Called when a player leaves. Arguments: the player's name, the player's UUID. | +| `chatMessage` | Called when a player sends a chat message. Arguments: the message, the player's name and UUID. | +| `pluginMessage` | Called when a client sends a [plugin message](https://wiki.vg/Plugin_channels). Arguments: the channel, the message, the player's name and UUID. | +| `command` | Called when a player runs a command. Arguments: the command, the arguments, the player's name and UUID. | ## The `server` table The `server` table is used to interact with the server. It has the following fields: -| Field | Description | -|---------------|--------------------------------------------------------------------------------------------------| -| `players` | A map from UUIDs to player names. | -| `sendMessage` | Send a player a message. Arguments: the player (name or UUID), the message. | -| `broadcast` | Broadcast a message to all online players. Arguments: the message. | -| `disconnect` | Disconnect a player from the server. Arguments: the player (name or UUID), the reason (optional) | +| Field | Description | +|---------------------|------------------------------------------------------------------------------------------------------------------------------------| +| `players` | A map from UUIDs to player names. | +| `sendPluginMessage` | Send a player a [plugin message](https://wiki.vg/Plugin_channels). Arguments: the player (name or UUID), the channel, the message. | +| `sendMessage` | Send a player a message. Arguments: the player (name or UUID), the message. | +| `broadcast` | Broadcast a message to all online players. Arguments: the message. | +| `disconnect` | Disconnect a player from the server. Arguments: the player (name or UUID), the reason (optional) | ## The `registry` table diff --git a/plugins/testcmd.lua b/plugins/testcmd.lua index 52771ba..e336a3c 100644 --- a/plugins/testcmd.lua +++ b/plugins/testcmd.lua @@ -20,4 +20,8 @@ function plugin.command(command, args, name, uuid) logger.info("player " .. name .. " ran /" .. command .. " " .. args) end +function plugin.playerJoin(name, uuid) + logger.info("player joined: " .. name .. " with uuid " .. uuid) +end + return plugin diff --git a/src/network/server.rs b/src/network/server.rs index 7786e83..768756e 100644 --- a/src/network/server.rs +++ b/src/network/server.rs @@ -135,7 +135,17 @@ impl <'lua> NetworkServer<'lua> { } } } + }, + Response::PluginMessage { player, channel, data } => { + for client in self.clients.iter_mut() { + if let Some(pl) = &client.player { + if pl.name == player || pl.uuid.to_string() == player { + client.send_packet(CPluginMessage { channel: channel.clone(), data: data.clone() })?; + } + } + } } +, } Ok(()) } @@ -181,6 +191,9 @@ impl <'lua> NetworkServer<'lua> { } } } + ServerBoundPacket::PluginMessage(SPluginMessage { channel, data }) => { + self.plugins.plugin_message(client.player.as_ref().unwrap(), &channel, &data); + } } Ok(()) } @@ -193,10 +206,12 @@ impl <'lua> NetworkServer<'lua> { client.close(); return Ok(()) } + client.player = Some(Player { name: login_start.name.clone(), uuid: login_start.uuid, }); + match self.config.login { LoginMode::Offline => { client.verified = true; @@ -226,9 +241,11 @@ impl <'lua> NetworkServer<'lua> { client.close(); return Ok(()); }; + let (sig, data) = data.split_at(32); let mut mac = Hmac::::new_from_slice(self.config.velocity_secret.clone().unwrap().as_bytes())?; mac.update(data); + if mac.verify_slice(sig).is_err() { client.send_packet(Disconnect { reason: json!({ "text": "Could not verify secret. Ensure that the secrets configured for Velocity and Quectocraft match." @@ -236,15 +253,21 @@ impl <'lua> NetworkServer<'lua> { client.close(); return Ok(()) } + client.verified = true; + client.send_packet(LoginPluginRequest{ id: -1, channel: "qc:init".to_owned(), data: Vec::new() })?; + Ok(()) } + // + // Handle the end of "login" and beginning of "play" + // fn login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> { if !client.verified { client.send_packet(Disconnect { reason: json!({ @@ -254,11 +277,14 @@ impl <'lua> NetworkServer<'lua> { client.close(); return Ok(()) } + client.send_packet(LoginSuccess { name: client.player.as_ref().unwrap().name.to_owned(), uuid: client.player.as_ref().unwrap().uuid, })?; + self.plugins.player_join(client.player.as_ref().unwrap()); + client.send_packet(LoginPlay { eid: client.id, is_hardcore: false, @@ -280,7 +306,8 @@ impl <'lua> NetworkServer<'lua> { is_flat: false, death_location: None, })?; - client.send_packet(PluginMessage { + + client.send_packet(CPluginMessage { channel: "minecraft:brand".to_owned(), data: { let mut data = Vec::new(); @@ -288,7 +315,10 @@ impl <'lua> NetworkServer<'lua> { data } })?; + client.send_packet(self.commands.clone())?; + + // Send 3x3 square of empty chunks. let mut chunk_data: Vec = Vec::new(); for _ in 0..(384 / 16) { // number of non-air blocks @@ -305,15 +335,21 @@ impl <'lua> NetworkServer<'lua> { let hmdata = vec![0i64; 37]; let mut heightmap = nbt::Blob::new(); heightmap.insert("MOTION_BLOCKING", hmdata).unwrap(); - client.send_packet(ChunkData { - x: 0, - z: 0, - heightmap, - chunk_data, - })?; + for x in -1..=1 { + for z in -1..=1 { + client.send_packet(ChunkData { + x, + z, + heightmap: heightmap.clone(), + chunk_data: chunk_data.clone(), + })?; + } + } + client.send_packet(SetDefaultSpawnPosition { pos: Position { x: 0, y: 0, z: 0 }, angle: 0.0 })?; + client.send_packet(SyncPlayerPosition { x: 0.0, y: 64.0, @@ -324,8 +360,7 @@ impl <'lua> NetworkServer<'lua> { teleport_id: 0, dismount: false })?; - // TODO why doesn't this work with quilt? - // client.send_packet(ClientBoundPacket::PlayerAbilities(0x0f, 0.05, 0.1))?; + Ok(()) } diff --git a/src/plugins/init.lua b/src/plugins/init.lua index 2166d97..1c0a763 100644 --- a/src/plugins/init.lua +++ b/src/plugins/init.lua @@ -32,6 +32,16 @@ function server.broadcast(message) table.insert(_qc.responses, { type = "broadcast", message = message }) end +function server.sendPluginMessage(player, channel, data) + if type(player) ~= "string" then + error("player must be a string") + end + if type(channel) ~= "string" then + error("channel must be a string") + end + table.insert(_qc.responses, {type = "plugin_message", player = player, channel = channel, data = data}) +end + function server.disconnect(player, reason) local reason = assert(to_chat(reason, { translate = "multiplayer.disconnect.generic" })) table.insert(_qc.responses, { type = "disconnect", player = player, reason = reason }) diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index fe36c16..cc2dba6 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -17,6 +17,8 @@ mod plugin; pub enum Response { #[serde(rename = "message")] Message { player: String, message: serde_json::Value }, + #[serde(rename = "plugin_message")] + PluginMessage { player: String, channel: String, data: Vec }, #[serde(rename = "broadcast")] Broadcast { message: serde_json::Value }, #[serde(rename = "disconnect")] @@ -180,4 +182,14 @@ impl <'lua> Plugins<'lua> { } } } + + pub fn plugin_message(&self, player: &Player, channel: &str, data: &[u8]) { + for pl in &self.plugins { + if let Some(func) = &pl.event_handlers.plugin_message { + if let Err(e) = func.call::<_, ()>((channel, data, player.name.as_str(), player.uuid.to_string())) { + warn!("Error in plugin {}: {}", pl.name, e); + } + } + } + } } diff --git a/src/plugins/plugin.rs b/src/plugins/plugin.rs index 3e248d9..7bd138c 100644 --- a/src/plugins/plugin.rs +++ b/src/plugins/plugin.rs @@ -9,6 +9,7 @@ pub struct EventHandlers<'lua> { pub player_leave: Option>, pub chat_message: Option>, pub command: Option>, + pub plugin_message: Option>, } pub struct Plugin<'lua> { @@ -33,8 +34,17 @@ impl <'lua> Plugin<'lua> { let player_leave: Option> = module.get("playerLeave").ok(); let chat_message: Option> = module.get("chatMessage").ok(); let command: Option> = module.get("command").ok(); + let plugin_message: Option> = module.get("pluginMessage").ok(); - let event_handlers = EventHandlers { init, register_commands, player_join, player_leave, chat_message, command }; + let event_handlers = EventHandlers { + init, + register_commands, + player_join, + player_leave, + chat_message, + command, + plugin_message, + }; Ok(Plugin { id, name, version, event_handlers }) } } diff --git a/src/protocol/clientbound.rs b/src/protocol/clientbound.rs index 1e50533..6461e56 100644 --- a/src/protocol/clientbound.rs +++ b/src/protocol/clientbound.rs @@ -151,12 +151,12 @@ impl ClientBoundPacket for LoginPlay { } #[derive(Debug)] -pub struct PluginMessage { +pub struct CPluginMessage { pub channel: String, pub data: Vec, } -impl ClientBoundPacket for PluginMessage { +impl ClientBoundPacket for CPluginMessage { fn encode(&self, encoder: &mut impl PacketEncoder) { encoder.write_string(32767, &self.channel); encoder.write_bytes(&self.data); diff --git a/src/protocol/serverbound.rs b/src/protocol/serverbound.rs index 07fc135..e5be41b 100644 --- a/src/protocol/serverbound.rs +++ b/src/protocol/serverbound.rs @@ -79,6 +79,20 @@ impl LoginPluginResponse { } } +#[derive(Debug)] +pub struct SPluginMessage { + pub channel: String, + pub data: Vec, +} + +impl SPluginMessage { + pub fn decode(mut decoder: PacketDecoder) -> Self { + let channel = decoder.read_string(); + let data = decoder.read_to_end().to_vec(); + Self { channel, data } + } +} + #[derive(Debug)] pub enum ServerBoundPacket { Unknown(i32), @@ -94,13 +108,14 @@ pub enum ServerBoundPacket { // play ChatMessage(ChatMessage), ChatCommand(ChatMessage), + PluginMessage(SPluginMessage), } impl ServerBoundPacket { pub fn decode(state: &mut NetworkState, mut decoder: PacketDecoder) -> ServerBoundPacket { use NetworkState as NS; match (*state, decoder.packet_id()) { - (NS::Handshake, 0) => { + (NS::Handshake, 0x00) => { let hs = Handshake::decode(decoder); match hs.next_state { 1 => *state = NS::Status, @@ -109,23 +124,24 @@ impl ServerBoundPacket { } ServerBoundPacket::Handshake(hs) }, - (NS::Status, 0) + (NS::Status, 0x00) => ServerBoundPacket::StatusRequest(), - (NS::Status, 1) + (NS::Status, 0x01) => ServerBoundPacket::PingRequest(decoder.read_long()), - (NS::Login, 0) => { + (NS::Login, 0x00) => { ServerBoundPacket::LoginStart(LoginStart::decode(decoder)) }, - (NS::Login, 2) => { + (NS::Login, 0x02) => { let lpr = LoginPluginResponse::decode(decoder); if lpr.id == -1 { *state = NetworkState::Play; } ServerBoundPacket::LoginPluginResponse(lpr) }, - (NS::Play, 4) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)), - (NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)), - (NS::Play, id @ (17 | 19 | 20 | 21 | 29)) => ServerBoundPacket::Ignored(id), + (NS::Play, 0x04) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)), + (NS::Play, 0x05) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)), + (NS::Play, 0x0C) => ServerBoundPacket::PluginMessage(SPluginMessage::decode(decoder)), + (NS::Play, id @ (0x11 | 0x13 | 0x14 | 0x15 | 0x1d)) => ServerBoundPacket::Ignored(id), (_, id) => ServerBoundPacket::Unknown(id), } }