Adds support for connecting to 1.7.10 modded servers using the FML|HS protocol: https://wiki.vg/Minecraft_Forge_Handshake * Handle client-bound plugin message packets * Parse FML|HS plugin channel messages * Add ModList serialization using Mod serializable, LenPrefixed<VarInt, Mod> * Save forge_mods from server ping and send in FML|HS ModList packet * Show Forge mod count in server ping listing * Send acknowledgements, completing the handshake * Add VarShort to custom payload len prefix replaces i16, fixes OOM on large modded servers * Add custom CoFHLib's SendUUID packet -26 See explanation at https://github.com/SpigotMC/BungeeCord/issues/1437 This packet is defined by CoFHLib in https://github.com/CoFH/CoFHLib/blob/1.7.10/src/main/java/cofh/lib/util/helpers/SecurityHelper.java#L40 Fixes thread '' panicked at 'bad packet id 0xffffffe6 in Clientbound Play' with FTB:IE
This commit is contained in:
parent
2f82d2ae71
commit
1476d0628a
|
@ -0,0 +1,180 @@
|
|||
|
||||
/// Implements https://wiki.vg/Minecraft_Forge_Handshake
|
||||
use std::io;
|
||||
use byteorder::WriteBytesExt;
|
||||
|
||||
use crate::protocol::{Serializable, Error, LenPrefixed, VarInt};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Phase {
|
||||
// Client handshake states (written)
|
||||
Start,
|
||||
WaitingServerData,
|
||||
WaitingServerComplete,
|
||||
PendingComplete,
|
||||
|
||||
// Server handshake states (read)
|
||||
WaitingCAck,
|
||||
|
||||
// Both client and server handshake states (different values on the wire)
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl Serializable for Phase {
|
||||
/// Read server handshake state from server
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
let phase: i8 = Serializable::read_from(buf)?;
|
||||
Ok(match phase {
|
||||
2 => Phase::WaitingCAck,
|
||||
3 => Phase::Complete,
|
||||
_ => panic!("bad FML|HS server phase: {}", phase),
|
||||
})
|
||||
}
|
||||
|
||||
/// Send client handshake state from client
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
buf.write_u8(match self {
|
||||
Phase::WaitingServerData => 2,
|
||||
Phase::WaitingServerComplete => 3,
|
||||
Phase::PendingComplete => 4,
|
||||
Phase::Complete => 5,
|
||||
_ => panic!("bad FML|HS client phase: {:?}", self),
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ForgeMod {
|
||||
pub modid: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Serializable for ForgeMod {
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
Ok(ForgeMod {
|
||||
modid: Serializable::read_from(buf)?,
|
||||
version: Serializable::read_from(buf)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
self.modid.write_to(buf)?;
|
||||
self.version.write_to(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModIdMapping {
|
||||
pub name: String,
|
||||
pub id: VarInt,
|
||||
}
|
||||
|
||||
impl Serializable for ModIdMapping {
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
Ok(ModIdMapping {
|
||||
name: Serializable::read_from(buf)?,
|
||||
id: Serializable::read_from(buf)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
self.name.write_to(buf)?;
|
||||
self.id.write_to(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FmlHs {
|
||||
ServerHello {
|
||||
fml_protocol_version: i8,
|
||||
override_dimension: Option<i32>,
|
||||
},
|
||||
ClientHello {
|
||||
fml_protocol_version: i8,
|
||||
},
|
||||
ModList {
|
||||
mods: LenPrefixed<VarInt, ForgeMod>,
|
||||
},
|
||||
/* TODO: 1.8+ https://wiki.vg/Minecraft_Forge_Handshake#Differences_from_Forge_1.7.10
|
||||
RegistryData {
|
||||
has_more: bool,
|
||||
name: String,
|
||||
ids: LenPrefixed<VarInt, ModIdMapping>,
|
||||
substitutions: LenPrefixed<VarInt, String>,
|
||||
dummies: LenPrefixed<VarInt, String>,
|
||||
},
|
||||
*/
|
||||
ModIdData {
|
||||
mappings: LenPrefixed<VarInt, ModIdMapping>,
|
||||
block_substitutions: LenPrefixed<VarInt, String>,
|
||||
item_substitutions: LenPrefixed<VarInt, String>,
|
||||
},
|
||||
HandshakeAck {
|
||||
phase: Phase,
|
||||
},
|
||||
HandshakeReset,
|
||||
}
|
||||
|
||||
impl Serializable for FmlHs {
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
let discriminator: u8 = Serializable::read_from(buf)?;
|
||||
|
||||
match discriminator {
|
||||
0 => {
|
||||
let fml_protocol_version: i8 = Serializable::read_from(buf)?;
|
||||
let override_dimension = if fml_protocol_version > 1 {
|
||||
let dimension: i32 = Serializable::read_from(buf)?;
|
||||
Some(dimension)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
println!("FML|HS ServerHello: fml_protocol_version={}, override_dimension={:?}", fml_protocol_version, override_dimension);
|
||||
|
||||
Ok(FmlHs::ServerHello {
|
||||
fml_protocol_version,
|
||||
override_dimension,
|
||||
})
|
||||
},
|
||||
1 => panic!("Received unexpected FML|HS ClientHello from server"),
|
||||
2 => {
|
||||
Ok(FmlHs::ModList {
|
||||
mods: Serializable::read_from(buf)?,
|
||||
})
|
||||
},
|
||||
3 => {
|
||||
Ok(FmlHs::ModIdData {
|
||||
mappings: Serializable::read_from(buf)?,
|
||||
block_substitutions: Serializable::read_from(buf)?,
|
||||
item_substitutions: Serializable::read_from(buf)?,
|
||||
})
|
||||
},
|
||||
255 => {
|
||||
Ok(FmlHs::HandshakeAck {
|
||||
phase: Serializable::read_from(buf)?,
|
||||
})
|
||||
},
|
||||
_ => panic!("Unhandled FML|HS packet: discriminator={}", discriminator),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
match self {
|
||||
FmlHs::ClientHello { fml_protocol_version } => {
|
||||
buf.write_u8(1)?;
|
||||
fml_protocol_version.write_to(buf)
|
||||
},
|
||||
FmlHs::ModList { mods } => {
|
||||
buf.write_u8(2)?;
|
||||
mods.write_to(buf)
|
||||
},
|
||||
FmlHs::HandshakeAck { phase } => {
|
||||
buf.write_u8(255)?;
|
||||
phase.write_to(buf)
|
||||
},
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ use serde_json;
|
|||
use reqwest;
|
||||
|
||||
pub mod mojang;
|
||||
pub mod forge;
|
||||
|
||||
use crate::nbt;
|
||||
use crate::format;
|
||||
|
@ -660,6 +661,65 @@ impl fmt::Debug for VarInt {
|
|||
}
|
||||
}
|
||||
|
||||
/// `VarShort` have a variable size (2 or 3 bytes) and are backwards-compatible
|
||||
/// with vanilla shorts, used for Forge custom payloads
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct VarShort(pub i32);
|
||||
|
||||
impl Lengthable for VarShort {
|
||||
fn into(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
|
||||
fn from(u: usize) -> VarShort {
|
||||
VarShort(u as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for VarShort {
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<VarShort, Error> {
|
||||
let low = buf.read_u16::<BigEndian>()? as u32;
|
||||
let val = if (low & 0x8000) != 0 {
|
||||
let high = buf.read_u8()? as u32;
|
||||
|
||||
(high << 15) | (low & 0x7fff)
|
||||
} else {
|
||||
low
|
||||
};
|
||||
|
||||
Result::Ok(VarShort(val as i32))
|
||||
}
|
||||
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
assert!(self.0 >= 0 && self.0 <= 0x7fffff, "VarShort invalid value: {}", self.0);
|
||||
let mut low = self.0 & 0x7fff;
|
||||
let high = (self.0 & 0x7f8000) >> 15;
|
||||
if high != 0 {
|
||||
low |= 0x8000;
|
||||
}
|
||||
|
||||
buf.write_u16::<BigEndian>(low as u16)?;
|
||||
|
||||
if high != 0 {
|
||||
buf.write_u8(high as u8)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl default::Default for VarShort {
|
||||
fn default() -> VarShort {
|
||||
VarShort(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VarShort {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// `VarLong` have a variable size (between 1 and 10 bytes) when encoded based
|
||||
/// on the size of the number
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -1005,6 +1065,29 @@ impl Conn {
|
|||
let version = val.get("version").ok_or(invalid_status())?;
|
||||
let players = val.get("players").ok_or(invalid_status())?;
|
||||
|
||||
// For modded servers, get the list of Forge mods installed
|
||||
let mut forge_mods: std::vec::Vec<crate::protocol::forge::ForgeMod> = vec![];
|
||||
if let Some(modinfo) = val.get("modinfo") {
|
||||
if let Some(modinfo_type) = modinfo.get("type") {
|
||||
if modinfo_type == "FML" {
|
||||
if let Some(modlist) = modinfo.get("modList") {
|
||||
if let Value::Array(items) = modlist {
|
||||
for item in items {
|
||||
if let Value::Object(obj) = item {
|
||||
let modid = obj.get("modid").unwrap().as_str().unwrap().to_string();
|
||||
let version = obj.get("version").unwrap().as_str().unwrap().to_string();
|
||||
|
||||
forge_mods.push(crate::protocol::forge::ForgeMod { modid, version });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Unrecognized modinfo type in server ping response: {} in {}", modinfo_type, modinfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((Status {
|
||||
version: StatusVersion {
|
||||
name: version.get("name").and_then(Value::as_str).ok_or(invalid_status())?
|
||||
|
@ -1025,6 +1108,7 @@ impl Conn {
|
|||
description: format::Component::from_value(val.get("description")
|
||||
.ok_or(invalid_status())?),
|
||||
favicon: val.get("favicon").and_then(Value::as_str).map(|v| v.to_owned()),
|
||||
forge_mods,
|
||||
},
|
||||
ping))
|
||||
}
|
||||
|
@ -1036,6 +1120,7 @@ pub struct Status {
|
|||
pub players: StatusPlayers,
|
||||
pub description: format::Component,
|
||||
pub favicon: Option<String>,
|
||||
pub forge_mods: Vec<crate::protocol::forge::ForgeMod>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -161,7 +161,7 @@ state_packets!(
|
|||
}
|
||||
packet PluginMessageServerbound_i16 {
|
||||
field channel: String =,
|
||||
field data: LenPrefixedBytes<i16> =,
|
||||
field data: LenPrefixedBytes<VarShort> =,
|
||||
}
|
||||
packet EditBook {
|
||||
field new_book: Option<item::Stack> =,
|
||||
|
@ -875,7 +875,7 @@ state_packets!(
|
|||
}
|
||||
packet PluginMessageClientbound_i16 {
|
||||
field channel: String =,
|
||||
field data: LenPrefixedBytes<i16> =,
|
||||
field data: LenPrefixedBytes<VarShort> =,
|
||||
}
|
||||
/// Plays a sound by name on the client
|
||||
packet NamedSoundEffect {
|
||||
|
@ -1747,6 +1747,9 @@ state_packets!(
|
|||
field id: VarInt =,
|
||||
field trades: LenPrefixed<u8, packet::Trade> =,
|
||||
}
|
||||
packet CoFHLib_SendUUID {
|
||||
field player_uuid: UUID =,
|
||||
}
|
||||
}
|
||||
}
|
||||
login Login {
|
||||
|
|
|
@ -99,6 +99,7 @@ protocol_packet_ids!(
|
|||
0x3e => Teams_NoVisColor
|
||||
0x3f => PluginMessageClientbound_i16
|
||||
0x40 => Disconnect
|
||||
-0x1a => CoFHLib_SendUUID
|
||||
}
|
||||
}
|
||||
login Login {
|
||||
|
|
Loading…
Reference in New Issue