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
30b91f4707
commit
327efcf043
|
@ -35,7 +35,7 @@ Join with your favorite IRC client or [Matrix](https://matrix.to/#/#_espernet_#s
|
||||||
| 1.9 | 107 | ✓ |
|
| 1.9 | 107 | ✓ |
|
||||||
| 15w39c | 74 | ✓ |
|
| 15w39c | 74 | ✓ |
|
||||||
| 1.8.9 | 47 | ✓ |
|
| 1.8.9 | 47 | ✓ |
|
||||||
| 1.7.10 | 5 | ✓ |
|
| 1.7.10 + Forge | 5 | ✓ |
|
||||||
|
|
||||||
Stevenarella is designed to support multiple protocol versions, so that client
|
Stevenarella is designed to support multiple protocol versions, so that client
|
||||||
development is not in lock-step with the server version. The level of
|
development is not in lock-step with the server version. The level of
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -88,16 +88,17 @@ pub struct Game {
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn connect_to(&mut self, address: &str) {
|
pub fn connect_to(&mut self, address: &str) {
|
||||||
let protocol_version = match protocol::Conn::new(&address, protocol::SUPPORTED_PROTOCOLS[0]).and_then(|conn| conn.do_status()) {
|
let (protocol_version, forge_mods) = match protocol::Conn::new(&address, protocol::SUPPORTED_PROTOCOLS[0])
|
||||||
Ok(res) => {
|
.and_then(|conn| conn.do_status()) {
|
||||||
info!("Detected server protocol version {}", res.0.version.protocol);
|
Ok(res) => {
|
||||||
res.0.version.protocol
|
info!("Detected server protocol version {}", res.0.version.protocol);
|
||||||
},
|
(res.0.version.protocol, res.0.forge_mods)
|
||||||
Err(err) => {
|
},
|
||||||
warn!("Error pinging server {} to get protocol version: {:?}, defaulting to {}", address, err, protocol::SUPPORTED_PROTOCOLS[0]);
|
Err(err) => {
|
||||||
protocol::SUPPORTED_PROTOCOLS[0]
|
warn!("Error pinging server {} to get protocol version: {:?}, defaulting to {}", address, err, protocol::SUPPORTED_PROTOCOLS[0]);
|
||||||
},
|
(protocol::SUPPORTED_PROTOCOLS[0], vec![])
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
self.connect_reply = Some(rx);
|
self.connect_reply = Some(rx);
|
||||||
|
@ -109,7 +110,7 @@ impl Game {
|
||||||
access_token: self.vars.get(auth::AUTH_TOKEN).clone(),
|
access_token: self.vars.get(auth::AUTH_TOKEN).clone(),
|
||||||
};
|
};
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
tx.send(server::Server::connect(resources, profile, &address, protocol_version)).unwrap();
|
tx.send(server::Server::connect(resources, profile, &address, protocol_version, forge_mods)).unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
use reqwest;
|
||||||
|
|
||||||
pub mod mojang;
|
pub mod mojang;
|
||||||
|
pub mod forge;
|
||||||
|
|
||||||
use crate::nbt;
|
use crate::nbt;
|
||||||
use crate::format;
|
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
|
/// `VarLong` have a variable size (between 1 and 10 bytes) when encoded based
|
||||||
/// on the size of the number
|
/// on the size of the number
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -1005,6 +1065,29 @@ impl Conn {
|
||||||
let version = val.get("version").ok_or(invalid_status())?;
|
let version = val.get("version").ok_or(invalid_status())?;
|
||||||
let players = val.get("players").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 {
|
Ok((Status {
|
||||||
version: StatusVersion {
|
version: StatusVersion {
|
||||||
name: version.get("name").and_then(Value::as_str).ok_or(invalid_status())?
|
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")
|
description: format::Component::from_value(val.get("description")
|
||||||
.ok_or(invalid_status())?),
|
.ok_or(invalid_status())?),
|
||||||
favicon: val.get("favicon").and_then(Value::as_str).map(|v| v.to_owned()),
|
favicon: val.get("favicon").and_then(Value::as_str).map(|v| v.to_owned()),
|
||||||
|
forge_mods,
|
||||||
},
|
},
|
||||||
ping))
|
ping))
|
||||||
}
|
}
|
||||||
|
@ -1036,6 +1120,7 @@ pub struct Status {
|
||||||
pub players: StatusPlayers,
|
pub players: StatusPlayers,
|
||||||
pub description: format::Component,
|
pub description: format::Component,
|
||||||
pub favicon: Option<String>,
|
pub favicon: Option<String>,
|
||||||
|
pub forge_mods: Vec<crate::protocol::forge::ForgeMod>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -161,7 +161,7 @@ state_packets!(
|
||||||
}
|
}
|
||||||
packet PluginMessageServerbound_i16 {
|
packet PluginMessageServerbound_i16 {
|
||||||
field channel: String =,
|
field channel: String =,
|
||||||
field data: LenPrefixedBytes<i16> =,
|
field data: LenPrefixedBytes<VarShort> =,
|
||||||
}
|
}
|
||||||
packet EditBook {
|
packet EditBook {
|
||||||
field new_book: Option<item::Stack> =,
|
field new_book: Option<item::Stack> =,
|
||||||
|
@ -875,7 +875,7 @@ state_packets!(
|
||||||
}
|
}
|
||||||
packet PluginMessageClientbound_i16 {
|
packet PluginMessageClientbound_i16 {
|
||||||
field channel: String =,
|
field channel: String =,
|
||||||
field data: LenPrefixedBytes<i16> =,
|
field data: LenPrefixedBytes<VarShort> =,
|
||||||
}
|
}
|
||||||
/// Plays a sound by name on the client
|
/// Plays a sound by name on the client
|
||||||
packet NamedSoundEffect {
|
packet NamedSoundEffect {
|
||||||
|
@ -1747,6 +1747,9 @@ state_packets!(
|
||||||
field id: VarInt =,
|
field id: VarInt =,
|
||||||
field trades: LenPrefixed<u8, packet::Trade> =,
|
field trades: LenPrefixed<u8, packet::Trade> =,
|
||||||
}
|
}
|
||||||
|
packet CoFHLib_SendUUID {
|
||||||
|
field player_uuid: UUID =,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
login Login {
|
login Login {
|
||||||
|
|
|
@ -99,6 +99,7 @@ protocol_packet_ids!(
|
||||||
0x3e => Teams_NoVisColor
|
0x3e => Teams_NoVisColor
|
||||||
0x3f => PluginMessageClientbound_i16
|
0x3f => PluginMessageClientbound_i16
|
||||||
0x40 => Disconnect
|
0x40 => Disconnect
|
||||||
|
-0x1a => CoFHLib_SendUUID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
login Login {
|
login Login {
|
||||||
|
|
|
@ -75,6 +75,7 @@ struct PingInfo {
|
||||||
max: i32,
|
max: i32,
|
||||||
protocol_version: i32,
|
protocol_version: i32,
|
||||||
protocol_name: String,
|
protocol_name: String,
|
||||||
|
forge_mods: Vec<crate::protocol::forge::ForgeMod>,
|
||||||
favicon: Option<image::DynamicImage>,
|
favicon: Option<image::DynamicImage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,6 +279,7 @@ impl ServerList {
|
||||||
max: res.0.players.max,
|
max: res.0.players.max,
|
||||||
protocol_version: res.0.version.protocol,
|
protocol_version: res.0.version.protocol,
|
||||||
protocol_name: res.0.version.name,
|
protocol_name: res.0.version.name,
|
||||||
|
forge_mods: res.0.forge_mods,
|
||||||
favicon,
|
favicon,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -293,6 +295,7 @@ impl ServerList {
|
||||||
max: 0,
|
max: 0,
|
||||||
protocol_version: 0,
|
protocol_version: 0,
|
||||||
protocol_name: "".to_owned(),
|
protocol_name: "".to_owned(),
|
||||||
|
forge_mods: vec![],
|
||||||
favicon: None,
|
favicon: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -489,7 +492,9 @@ impl super::Screen for ServerList {
|
||||||
};
|
};
|
||||||
players.text = txt;
|
players.text = txt;
|
||||||
}
|
}
|
||||||
let mut txt = TextComponent::new(&res.protocol_name);
|
let sm = format!("{} mods + {}", res.forge_mods.len(), res.protocol_name);
|
||||||
|
let st = if res.forge_mods.len() > 0 { &sm } else { &res.protocol_name };
|
||||||
|
let mut txt = TextComponent::new(&st);
|
||||||
txt.modifier.color = Some(format::Color::Yellow);
|
txt.modifier.color = Some(format::Color::Yellow);
|
||||||
let mut msg = Component::Text(txt);
|
let mut msg = Component::Text(txt);
|
||||||
format::convert_legacy(&mut msg);
|
format::convert_legacy(&mut msg);
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::protocol::{self, mojang, packet};
|
use crate::protocol::{self, mojang, packet, forge};
|
||||||
use crate::world;
|
use crate::world;
|
||||||
use crate::world::block;
|
use crate::world::block;
|
||||||
use rand::{self, Rng};
|
use rand::{self, Rng};
|
||||||
|
@ -42,6 +42,7 @@ pub struct Server {
|
||||||
uuid: protocol::UUID,
|
uuid: protocol::UUID,
|
||||||
conn: Option<protocol::Conn>,
|
conn: Option<protocol::Conn>,
|
||||||
protocol_version: i32,
|
protocol_version: i32,
|
||||||
|
forge_mods: Vec<forge::ForgeMod>,
|
||||||
read_queue: Option<mpsc::Receiver<Result<packet::Packet, protocol::Error>>>,
|
read_queue: Option<mpsc::Receiver<Result<packet::Packet, protocol::Error>>>,
|
||||||
pub disconnect_reason: Option<format::Component>,
|
pub disconnect_reason: Option<format::Component>,
|
||||||
just_disconnected: bool,
|
just_disconnected: bool,
|
||||||
|
@ -104,7 +105,7 @@ macro_rules! handle_packet {
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
|
||||||
pub fn connect(resources: Arc<RwLock<resources::Manager>>, profile: mojang::Profile, address: &str, protocol_version: i32) -> Result<Server, protocol::Error> {
|
pub fn connect(resources: Arc<RwLock<resources::Manager>>, profile: mojang::Profile, address: &str, protocol_version: i32, forge_mods: Vec<forge::ForgeMod>) -> Result<Server, protocol::Error> {
|
||||||
let mut conn = protocol::Conn::new(address, protocol_version)?;
|
let mut conn = protocol::Conn::new(address, protocol_version)?;
|
||||||
|
|
||||||
let host = conn.host.clone();
|
let host = conn.host.clone();
|
||||||
|
@ -147,7 +148,7 @@ impl Server {
|
||||||
read.state = protocol::State::Play;
|
read.state = protocol::State::Play;
|
||||||
write.state = protocol::State::Play;
|
write.state = protocol::State::Play;
|
||||||
let rx = Self::spawn_reader(read);
|
let rx = Self::spawn_reader(read);
|
||||||
return Ok(Server::new(protocol_version, protocol::UUID::from_str(&val.uuid), resources, Some(write), Some(rx)));
|
return Ok(Server::new(protocol_version, forge_mods, protocol::UUID::from_str(&val.uuid), resources, Some(write), Some(rx)));
|
||||||
}
|
}
|
||||||
protocol::packet::Packet::LoginDisconnect(val) => return Err(protocol::Error::Disconnect(val.reason)),
|
protocol::packet::Packet::LoginDisconnect(val) => return Err(protocol::Error::Disconnect(val.reason)),
|
||||||
val => return Err(protocol::Error::Err(format!("Wrong packet: {:?}", val))),
|
val => return Err(protocol::Error::Err(format!("Wrong packet: {:?}", val))),
|
||||||
|
@ -205,7 +206,7 @@ impl Server {
|
||||||
|
|
||||||
let rx = Self::spawn_reader(read);
|
let rx = Self::spawn_reader(read);
|
||||||
|
|
||||||
Ok(Server::new(protocol_version, protocol::UUID::from_str(&uuid), resources, Some(write), Some(rx)))
|
Ok(Server::new(protocol_version, forge_mods, protocol::UUID::from_str(&uuid), resources, Some(write), Some(rx)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_reader(mut read: protocol::Conn) -> mpsc::Receiver<Result<packet::Packet, protocol::Error>> {
|
fn spawn_reader(mut read: protocol::Conn) -> mpsc::Receiver<Result<packet::Packet, protocol::Error>> {
|
||||||
|
@ -226,7 +227,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dummy_server(resources: Arc<RwLock<resources::Manager>>) -> Server {
|
pub fn dummy_server(resources: Arc<RwLock<resources::Manager>>) -> Server {
|
||||||
let mut server = Server::new(protocol::SUPPORTED_PROTOCOLS[0], protocol::UUID::default(), resources, None, None);
|
let mut server = Server::new(protocol::SUPPORTED_PROTOCOLS[0], vec![], protocol::UUID::default(), resources, None, None);
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
for x in -7*16 .. 7*16 {
|
for x in -7*16 .. 7*16 {
|
||||||
for z in -7*16 .. 7*16 {
|
for z in -7*16 .. 7*16 {
|
||||||
|
@ -263,6 +264,7 @@ impl Server {
|
||||||
|
|
||||||
fn new(
|
fn new(
|
||||||
protocol_version: i32,
|
protocol_version: i32,
|
||||||
|
forge_mods: Vec<forge::ForgeMod>,
|
||||||
uuid: protocol::UUID,
|
uuid: protocol::UUID,
|
||||||
resources: Arc<RwLock<resources::Manager>>,
|
resources: Arc<RwLock<resources::Manager>>,
|
||||||
conn: Option<protocol::Conn>, read_queue: Option<mpsc::Receiver<Result<packet::Packet, protocol::Error>>>
|
conn: Option<protocol::Conn>, read_queue: Option<mpsc::Receiver<Result<packet::Packet, protocol::Error>>>
|
||||||
|
@ -279,6 +281,7 @@ impl Server {
|
||||||
uuid,
|
uuid,
|
||||||
conn,
|
conn,
|
||||||
protocol_version,
|
protocol_version,
|
||||||
|
forge_mods,
|
||||||
read_queue,
|
read_queue,
|
||||||
disconnect_reason: None,
|
disconnect_reason: None,
|
||||||
just_disconnected: false,
|
just_disconnected: false,
|
||||||
|
@ -388,6 +391,8 @@ impl Server {
|
||||||
match pck {
|
match pck {
|
||||||
Ok(pck) => handle_packet!{
|
Ok(pck) => handle_packet!{
|
||||||
self pck {
|
self pck {
|
||||||
|
PluginMessageClientbound_i16 => on_plugin_message_clientbound_i16,
|
||||||
|
PluginMessageClientbound => on_plugin_message_clientbound_1,
|
||||||
JoinGame_i32_ViewDistance => on_game_join_i32_viewdistance,
|
JoinGame_i32_ViewDistance => on_game_join_i32_viewdistance,
|
||||||
JoinGame_i32 => on_game_join_i32,
|
JoinGame_i32 => on_game_join_i32,
|
||||||
JoinGame_i8 => on_game_join_i8,
|
JoinGame_i8 => on_game_join_i8,
|
||||||
|
@ -667,6 +672,86 @@ impl Server {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_plugin_message_clientbound_i16(&mut self, msg: packet::play::clientbound::PluginMessageClientbound_i16) {
|
||||||
|
self.on_plugin_message_clientbound(&msg.channel, msg.data.data.as_slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_plugin_message_clientbound_1(&mut self, msg: packet::play::clientbound::PluginMessageClientbound) {
|
||||||
|
self.on_plugin_message_clientbound(&msg.channel, &msg.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_plugin_message_clientbound(&mut self, channel: &str, data: &[u8]) {
|
||||||
|
println!("Received plugin message: channel={}, data={:?}", channel, data);
|
||||||
|
|
||||||
|
match channel {
|
||||||
|
// TODO: "REGISTER" =>
|
||||||
|
// TODO: "UNREGISTER" =>
|
||||||
|
"FML|HS" => {
|
||||||
|
let msg = crate::protocol::Serializable::read_from(&mut std::io::Cursor::new(data)).unwrap();
|
||||||
|
println!("FML|HS msg={:?}", msg);
|
||||||
|
|
||||||
|
use forge::FmlHs::*;
|
||||||
|
use forge::Phase::*;
|
||||||
|
match msg {
|
||||||
|
ServerHello { fml_protocol_version, override_dimension } => {
|
||||||
|
println!("Received FML|HS ServerHello {} {:?}", fml_protocol_version, override_dimension);
|
||||||
|
|
||||||
|
self.write_plugin_message("REGISTER", "FML|HS\0FML\0FML|MP\0FML\0FORGE".as_bytes());
|
||||||
|
self.write_fmlhs_plugin_message(&ClientHello { fml_protocol_version });
|
||||||
|
// Send stashed mods list received from ping packet, client matching server
|
||||||
|
let mods = crate::protocol::LenPrefixed::<crate::protocol::VarInt, forge::ForgeMod>::new(self.forge_mods.clone());
|
||||||
|
self.write_fmlhs_plugin_message(&ModList { mods });
|
||||||
|
},
|
||||||
|
ModList { mods } => {
|
||||||
|
println!("Received FML|HS ModList: {:?}", mods);
|
||||||
|
|
||||||
|
self.write_fmlhs_plugin_message(&HandshakeAck { phase: WaitingServerData });
|
||||||
|
},
|
||||||
|
ModIdData { mappings: _, block_substitutions: _, item_substitutions: _ } => {
|
||||||
|
self.write_fmlhs_plugin_message(&HandshakeAck { phase: WaitingServerData });
|
||||||
|
},
|
||||||
|
HandshakeAck { phase } => {
|
||||||
|
match phase {
|
||||||
|
WaitingCAck => {
|
||||||
|
self.write_fmlhs_plugin_message(&HandshakeAck { phase: PendingComplete });
|
||||||
|
},
|
||||||
|
Complete => {
|
||||||
|
println!("FML|HS handshake complete!");
|
||||||
|
},
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_fmlhs_plugin_message(&mut self, msg: &forge::FmlHs) {
|
||||||
|
use crate::protocol::Serializable;
|
||||||
|
|
||||||
|
let mut buf: Vec<u8> = vec![];
|
||||||
|
msg.write_to(&mut buf).unwrap();
|
||||||
|
|
||||||
|
self.write_plugin_message("FML|HS", &buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_plugin_message(&mut self, channel: &str, data: &[u8]) {
|
||||||
|
println!("Sending plugin message: channel={}, data={:?}", channel, data);
|
||||||
|
if self.protocol_version >= 47 {
|
||||||
|
self.write_packet(packet::play::serverbound::PluginMessageServerbound {
|
||||||
|
channel: channel.to_string(),
|
||||||
|
data: data.to_vec(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.write_packet(packet::play::serverbound::PluginMessageServerbound_i16 {
|
||||||
|
channel: channel.to_string(),
|
||||||
|
data: crate::protocol::LenPrefixedBytes::<protocol::VarShort>::new(data.to_vec()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_game_join_i32_viewdistance(&mut self, join: packet::play::clientbound::JoinGame_i32_ViewDistance) {
|
fn on_game_join_i32_viewdistance(&mut self, join: packet::play::clientbound::JoinGame_i32_ViewDistance) {
|
||||||
self.on_game_join(join.gamemode, join.entity_id)
|
self.on_game_join(join.gamemode, join.entity_id)
|
||||||
}
|
}
|
||||||
|
@ -702,6 +787,7 @@ impl Server {
|
||||||
let brand = plugin_messages::Brand {
|
let brand = plugin_messages::Brand {
|
||||||
brand: "Steven".into(),
|
brand: "Steven".into(),
|
||||||
};
|
};
|
||||||
|
// TODO: refactor with write_plugin_message
|
||||||
if self.protocol_version >= 47 {
|
if self.protocol_version >= 47 {
|
||||||
self.write_packet(brand.as_message());
|
self.write_packet(brand.as_message());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
use crate::protocol::Serializable;
|
|
||||||
use crate::protocol::packet::play::serverbound::PluginMessageServerbound;
|
use crate::protocol::packet::play::serverbound::PluginMessageServerbound;
|
||||||
use crate::protocol::packet::play::serverbound::PluginMessageServerbound_i16;
|
use crate::protocol::packet::play::serverbound::PluginMessageServerbound_i16;
|
||||||
|
use crate::protocol::{Serializable, VarShort};
|
||||||
|
|
||||||
pub struct Brand {
|
pub struct Brand {
|
||||||
pub brand: String,
|
pub brand: String,
|
||||||
|
@ -31,7 +31,7 @@ impl Brand {
|
||||||
Serializable::write_to(&self.brand, &mut data).unwrap();
|
Serializable::write_to(&self.brand, &mut data).unwrap();
|
||||||
PluginMessageServerbound_i16 {
|
PluginMessageServerbound_i16 {
|
||||||
channel: "MC|Brand".into(),
|
channel: "MC|Brand".into(),
|
||||||
data: crate::protocol::LenPrefixedBytes::<i16>::new(data),
|
data: crate::protocol::LenPrefixedBytes::<VarShort>::new(data),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue