Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
638c15e
Add packet logging for debugging
iceiix May 3, 2019
3b4af94
Handle client-bound plugin message packets
iceiix May 3, 2019
4e5d916
Parse the FML|HS ServerHello message
iceiix May 3, 2019
a97f9c2
Move plugin message handling to plugin_messages::PluginMessageHandler
iceiix May 3, 2019
4d5631e
Revert "Move plugin message handling to plugin_messages::PluginMessag…
iceiix May 3, 2019
0324d49
Add enum FmlHs in plugin_messages
iceiix May 3, 2019
8b79913
Add from_message FML|HS parsing in plugin_messages::FmlHs
iceiix May 4, 2019
2fc8cc6
Send reply to ServerHello: REGISTER and FML|HS ClientHello
iceiix May 4, 2019
982ed64
Fix sending discriminator byte for ClientHello
iceiix May 4, 2019
b6faf19
Send empty ModList packet
iceiix May 4, 2019
8db6bea
Handle ModList packet from server
iceiix May 4, 2019
2c76dc8
Move FmlHs parsing to read_from Serializable trait instead of one-off…
iceiix May 4, 2019
0c79130
write_to Serializable trait for FmlHs
iceiix May 4, 2019
c3d2212
Add ModList serialization using Mod serializable, LenPrefixed<VarInt,…
iceiix May 4, 2019
e577ed4
Remove as_message(), add write_fmlhs_plugin_message()
iceiix May 4, 2019
f047355
Mod -> ForgeMod
iceiix May 4, 2019
3f9dc31
ForgeMod name -> modid
iceiix May 4, 2019
492a1a0
Parse modinfo.modList from server ping packet
iceiix May 4, 2019
0493405
Detect server protocol version in connect_to() instead of server ping…
iceiix May 4, 2019
8762150
Merge branch 'master' into forge
iceiix May 5, 2019
ed2c55f
Merge branch 'master' into forge
iceiix May 5, 2019
0ec20f6
Save forge_mods from server ping
iceiix May 5, 2019
ad94824
Remove Game instance variables protocol_version/forge_mods, not used …
iceiix May 5, 2019
87364bf
Send stashed mod list from ping packet to FML|HS ModList packet
iceiix May 5, 2019
1442904
Receive server ModList from FML|HS handshake
iceiix May 5, 2019
87f2328
Show Forge mod count in server ping listing
iceiix May 5, 2019
cc5f244
Add state machine, send handshake acknowledgement, phases
iceiix May 5, 2019
d6a957c
Begin to parse RegistryData
iceiix May 5, 2019
515cfb5
Merge branch 'master' into forge
iceiix May 5, 2019
b1a8fdf
Parse the ModIdData message in 1.7.10, not RegistryData
iceiix May 5, 2019
ed685d2
Receive server HandshakeAcks
iceiix May 5, 2019
3d15ed4
Send acknowledgements, handshake completes
iceiix May 5, 2019
d1a47dc
Add VarShort to custom payload len prefix replaces i16, fixes OOM on …
iceiix May 5, 2019
6a8326f
Add custom CoFHLib's SendUUID packet -26
iceiix May 5, 2019
95f660f
Merge branch 'master' into forge
iceiix May 5, 2019
9cab76d
Update comments
iceiix May 5, 2019
20ccecb
Merge branch 'master' into forge
iceiix May 6, 2019
515aae7
Move Forge-related code to src/protocol/forge.rs
iceiix May 6, 2019
c96e723
Remove fmlhs_state, doesn't seem to be necessary
iceiix May 6, 2019
6241df7
Cleanup
iceiix May 6, 2019
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
23 changes: 12 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,17 @@ pub struct Game {

impl Game {
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()) {
Ok(res) => {
info!("Detected server protocol version {}", res.0.version.protocol);
res.0.version.protocol
},
Err(err) => {
warn!("Error pinging server {} to get protocol version: {:?}, defaulting to {}", address, err, protocol::SUPPORTED_PROTOCOLS[0]);
protocol::SUPPORTED_PROTOCOLS[0]
},
};
let (protocol_version, forge_mods) = match protocol::Conn::new(&address, protocol::SUPPORTED_PROTOCOLS[0])
.and_then(|conn| conn.do_status()) {
Ok(res) => {
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]);
(protocol::SUPPORTED_PROTOCOLS[0], vec![])
},
};

let (tx, rx) = mpsc::channel();
self.connect_reply = Some(rx);
Expand All @@ -109,7 +110,7 @@ impl Game {
access_token: self.vars.get(auth::AUTH_TOKEN).clone(),
};
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();
});
}

Expand Down
180 changes: 180 additions & 0 deletions src/protocol/forge.rs
Original file line number Diff line number Diff line change
@@ -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!()
}
}
}
85 changes: 85 additions & 0 deletions src/protocol/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use serde_json;
use reqwest;

pub mod mojang;
pub mod forge;

use crate::nbt;
use crate::format;
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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())?
Expand All @@ -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))
}
Expand All @@ -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)]
Expand Down
7 changes: 5 additions & 2 deletions src/protocol/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> =,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1747,6 +1747,9 @@ state_packets!(
field id: VarInt =,
field trades: LenPrefixed<u8, packet::Trade> =,
}
packet CoFHLib_SendUUID {
field player_uuid: UUID =,
}
}
}
login Login {
Expand Down
1 change: 1 addition & 0 deletions src/protocol/versions/v1_7_10.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ protocol_packet_ids!(
0x3e => Teams_NoVisColor
0x3f => PluginMessageClientbound_i16
0x40 => Disconnect
-0x1a => CoFHLib_SendUUID
}
}
login Login {
Expand Down
Loading