Skip to content

Commit 31a89a7

Browse files
committed
Add Forge handshake support. Closes #88 (#134)
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 SpigotMC/BungeeCord#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
1 parent a49386e commit 31a89a7

File tree

4 files changed

+271
-2
lines changed

4 files changed

+271
-2
lines changed

protocol/forge.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
2+
/// Implements https://wiki.vg/Minecraft_Forge_Handshake
3+
use std::io;
4+
use byteorder::WriteBytesExt;
5+
6+
use crate::protocol::{Serializable, Error, LenPrefixed, VarInt};
7+
8+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9+
pub enum Phase {
10+
// Client handshake states (written)
11+
Start,
12+
WaitingServerData,
13+
WaitingServerComplete,
14+
PendingComplete,
15+
16+
// Server handshake states (read)
17+
WaitingCAck,
18+
19+
// Both client and server handshake states (different values on the wire)
20+
Complete,
21+
}
22+
23+
impl Serializable for Phase {
24+
/// Read server handshake state from server
25+
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
26+
let phase: i8 = Serializable::read_from(buf)?;
27+
Ok(match phase {
28+
2 => Phase::WaitingCAck,
29+
3 => Phase::Complete,
30+
_ => panic!("bad FML|HS server phase: {}", phase),
31+
})
32+
}
33+
34+
/// Send client handshake state from client
35+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
36+
buf.write_u8(match self {
37+
Phase::WaitingServerData => 2,
38+
Phase::WaitingServerComplete => 3,
39+
Phase::PendingComplete => 4,
40+
Phase::Complete => 5,
41+
_ => panic!("bad FML|HS client phase: {:?}", self),
42+
})?;
43+
Ok(())
44+
}
45+
}
46+
47+
48+
#[derive(Clone, Debug, Default)]
49+
pub struct ForgeMod {
50+
pub modid: String,
51+
pub version: String,
52+
}
53+
54+
impl Serializable for ForgeMod {
55+
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
56+
Ok(ForgeMod {
57+
modid: Serializable::read_from(buf)?,
58+
version: Serializable::read_from(buf)?,
59+
})
60+
}
61+
62+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
63+
self.modid.write_to(buf)?;
64+
self.version.write_to(buf)
65+
}
66+
}
67+
68+
#[derive(Debug)]
69+
pub struct ModIdMapping {
70+
pub name: String,
71+
pub id: VarInt,
72+
}
73+
74+
impl Serializable for ModIdMapping {
75+
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
76+
Ok(ModIdMapping {
77+
name: Serializable::read_from(buf)?,
78+
id: Serializable::read_from(buf)?,
79+
})
80+
}
81+
82+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
83+
self.name.write_to(buf)?;
84+
self.id.write_to(buf)
85+
}
86+
}
87+
88+
#[derive(Debug)]
89+
pub enum FmlHs {
90+
ServerHello {
91+
fml_protocol_version: i8,
92+
override_dimension: Option<i32>,
93+
},
94+
ClientHello {
95+
fml_protocol_version: i8,
96+
},
97+
ModList {
98+
mods: LenPrefixed<VarInt, ForgeMod>,
99+
},
100+
/* TODO: 1.8+ https://wiki.vg/Minecraft_Forge_Handshake#Differences_from_Forge_1.7.10
101+
RegistryData {
102+
has_more: bool,
103+
name: String,
104+
ids: LenPrefixed<VarInt, ModIdMapping>,
105+
substitutions: LenPrefixed<VarInt, String>,
106+
dummies: LenPrefixed<VarInt, String>,
107+
},
108+
*/
109+
ModIdData {
110+
mappings: LenPrefixed<VarInt, ModIdMapping>,
111+
block_substitutions: LenPrefixed<VarInt, String>,
112+
item_substitutions: LenPrefixed<VarInt, String>,
113+
},
114+
HandshakeAck {
115+
phase: Phase,
116+
},
117+
HandshakeReset,
118+
}
119+
120+
impl Serializable for FmlHs {
121+
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
122+
let discriminator: u8 = Serializable::read_from(buf)?;
123+
124+
match discriminator {
125+
0 => {
126+
let fml_protocol_version: i8 = Serializable::read_from(buf)?;
127+
let override_dimension = if fml_protocol_version > 1 {
128+
let dimension: i32 = Serializable::read_from(buf)?;
129+
Some(dimension)
130+
} else {
131+
None
132+
};
133+
134+
println!("FML|HS ServerHello: fml_protocol_version={}, override_dimension={:?}", fml_protocol_version, override_dimension);
135+
136+
Ok(FmlHs::ServerHello {
137+
fml_protocol_version,
138+
override_dimension,
139+
})
140+
},
141+
1 => panic!("Received unexpected FML|HS ClientHello from server"),
142+
2 => {
143+
Ok(FmlHs::ModList {
144+
mods: Serializable::read_from(buf)?,
145+
})
146+
},
147+
3 => {
148+
Ok(FmlHs::ModIdData {
149+
mappings: Serializable::read_from(buf)?,
150+
block_substitutions: Serializable::read_from(buf)?,
151+
item_substitutions: Serializable::read_from(buf)?,
152+
})
153+
},
154+
255 => {
155+
Ok(FmlHs::HandshakeAck {
156+
phase: Serializable::read_from(buf)?,
157+
})
158+
},
159+
_ => panic!("Unhandled FML|HS packet: discriminator={}", discriminator),
160+
}
161+
}
162+
163+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
164+
match self {
165+
FmlHs::ClientHello { fml_protocol_version } => {
166+
buf.write_u8(1)?;
167+
fml_protocol_version.write_to(buf)
168+
},
169+
FmlHs::ModList { mods } => {
170+
buf.write_u8(2)?;
171+
mods.write_to(buf)
172+
},
173+
FmlHs::HandshakeAck { phase } => {
174+
buf.write_u8(255)?;
175+
phase.write_to(buf)
176+
},
177+
_ => unimplemented!()
178+
}
179+
}
180+
}

protocol/mod.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use serde_json;
2323
use reqwest;
2424

2525
pub mod mojang;
26+
pub mod forge;
2627

2728
use crate::nbt;
2829
use crate::format;
@@ -660,6 +661,65 @@ impl fmt::Debug for VarInt {
660661
}
661662
}
662663

664+
/// `VarShort` have a variable size (2 or 3 bytes) and are backwards-compatible
665+
/// with vanilla shorts, used for Forge custom payloads
666+
#[derive(Clone, Copy)]
667+
pub struct VarShort(pub i32);
668+
669+
impl Lengthable for VarShort {
670+
fn into(self) -> usize {
671+
self.0 as usize
672+
}
673+
674+
fn from(u: usize) -> VarShort {
675+
VarShort(u as i32)
676+
}
677+
}
678+
679+
impl Serializable for VarShort {
680+
fn read_from<R: io::Read>(buf: &mut R) -> Result<VarShort, Error> {
681+
let low = buf.read_u16::<BigEndian>()? as u32;
682+
let val = if (low & 0x8000) != 0 {
683+
let high = buf.read_u8()? as u32;
684+
685+
(high << 15) | (low & 0x7fff)
686+
} else {
687+
low
688+
};
689+
690+
Result::Ok(VarShort(val as i32))
691+
}
692+
693+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
694+
assert!(self.0 >= 0 && self.0 <= 0x7fffff, "VarShort invalid value: {}", self.0);
695+
let mut low = self.0 & 0x7fff;
696+
let high = (self.0 & 0x7f8000) >> 15;
697+
if high != 0 {
698+
low |= 0x8000;
699+
}
700+
701+
buf.write_u16::<BigEndian>(low as u16)?;
702+
703+
if high != 0 {
704+
buf.write_u8(high as u8)?;
705+
}
706+
707+
Ok(())
708+
}
709+
}
710+
711+
impl default::Default for VarShort {
712+
fn default() -> VarShort {
713+
VarShort(0)
714+
}
715+
}
716+
717+
impl fmt::Debug for VarShort {
718+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
719+
write!(f, "{}", self.0)
720+
}
721+
}
722+
663723
/// `VarLong` have a variable size (between 1 and 10 bytes) when encoded based
664724
/// on the size of the number
665725
#[derive(Clone, Copy)]
@@ -1005,6 +1065,29 @@ impl Conn {
10051065
let version = val.get("version").ok_or(invalid_status())?;
10061066
let players = val.get("players").ok_or(invalid_status())?;
10071067

1068+
// For modded servers, get the list of Forge mods installed
1069+
let mut forge_mods: std::vec::Vec<crate::protocol::forge::ForgeMod> = vec![];
1070+
if let Some(modinfo) = val.get("modinfo") {
1071+
if let Some(modinfo_type) = modinfo.get("type") {
1072+
if modinfo_type == "FML" {
1073+
if let Some(modlist) = modinfo.get("modList") {
1074+
if let Value::Array(items) = modlist {
1075+
for item in items {
1076+
if let Value::Object(obj) = item {
1077+
let modid = obj.get("modid").unwrap().as_str().unwrap().to_string();
1078+
let version = obj.get("version").unwrap().as_str().unwrap().to_string();
1079+
1080+
forge_mods.push(crate::protocol::forge::ForgeMod { modid, version });
1081+
}
1082+
}
1083+
}
1084+
}
1085+
} else {
1086+
panic!("Unrecognized modinfo type in server ping response: {} in {}", modinfo_type, modinfo);
1087+
}
1088+
}
1089+
}
1090+
10081091
Ok((Status {
10091092
version: StatusVersion {
10101093
name: version.get("name").and_then(Value::as_str).ok_or(invalid_status())?
@@ -1025,6 +1108,7 @@ impl Conn {
10251108
description: format::Component::from_value(val.get("description")
10261109
.ok_or(invalid_status())?),
10271110
favicon: val.get("favicon").and_then(Value::as_str).map(|v| v.to_owned()),
1111+
forge_mods,
10281112
},
10291113
ping))
10301114
}
@@ -1036,6 +1120,7 @@ pub struct Status {
10361120
pub players: StatusPlayers,
10371121
pub description: format::Component,
10381122
pub favicon: Option<String>,
1123+
pub forge_mods: Vec<crate::protocol::forge::ForgeMod>,
10391124
}
10401125

10411126
#[derive(Debug)]

protocol/packet.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ state_packets!(
161161
}
162162
packet PluginMessageServerbound_i16 {
163163
field channel: String =,
164-
field data: LenPrefixedBytes<i16> =,
164+
field data: LenPrefixedBytes<VarShort> =,
165165
}
166166
packet EditBook {
167167
field new_book: Option<item::Stack> =,
@@ -875,7 +875,7 @@ state_packets!(
875875
}
876876
packet PluginMessageClientbound_i16 {
877877
field channel: String =,
878-
field data: LenPrefixedBytes<i16> =,
878+
field data: LenPrefixedBytes<VarShort> =,
879879
}
880880
/// Plays a sound by name on the client
881881
packet NamedSoundEffect {
@@ -1747,6 +1747,9 @@ state_packets!(
17471747
field id: VarInt =,
17481748
field trades: LenPrefixed<u8, packet::Trade> =,
17491749
}
1750+
packet CoFHLib_SendUUID {
1751+
field player_uuid: UUID =,
1752+
}
17501753
}
17511754
}
17521755
login Login {

protocol/versions/v1_7_10.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ protocol_packet_ids!(
9999
0x3e => Teams_NoVisColor
100100
0x3f => PluginMessageClientbound_i16
101101
0x40 => Disconnect
102+
-0x1a => CoFHLib_SendUUID
102103
}
103104
}
104105
login Login {

0 commit comments

Comments
 (0)