Skip to content

Commit 6b96162

Browse files
authored
forge: add FML2 protocol support (1.13.2-1.16.5+), fixes #400 (#494)
Allows connecting to newer Forge servers, 1.13.2 to 1.16.5 at least, which use the FML2 handshake protocol: https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29 Tested with a modded 1.16.5 server Extends #88 #134 Forge FML (v1, for 1.7.10 - 1.12.2) * protocol: update Cargo.lock * protocol: send FML2 on fmlNetworkVersion: 2 * protocol: factor out read_raw_packet_from() * protocol: move plugin message writing from Server to Conn; add write_fml2_handshake_plugin_message * protocol: CommandNode: add forge:modid, forge:enum * forge: add fml2 handshake packets * server: handle fml:loginwrapper fml::handshake packets
1 parent 1a257e2 commit 6b96162

File tree

7 files changed

+321
-45
lines changed

7 files changed

+321
-45
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ development is not in lock-step with the server version. The level of
5555
support varies, but the goal is to support major versions from 1.7.10
5656
up to the current latest major version. Occasionally, snapshots are also supported.
5757

58-
Forge servers are currently supported on 1.7.10 - 1.12.2.
58+
Forge servers are supported on 1.7.10 - 1.12.2 (FML) and 1.13.2 - 1.16.5 (FML2).
5959

6060
Support for older protocols will _not_ be dropped as newer protocols are added.
6161

protocol/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protocol/src/protocol/forge.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,121 @@ impl Serializable for FmlHs {
191191
}
192192
}
193193
}
194+
195+
pub mod fml2 {
196+
// https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29
197+
use super::*;
198+
199+
#[derive(Clone, Default, Debug)]
200+
pub struct Channel {
201+
pub name: String,
202+
pub version: String,
203+
}
204+
205+
impl Serializable for Channel {
206+
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
207+
Ok(Channel {
208+
name: Serializable::read_from(buf)?,
209+
version: Serializable::read_from(buf)?,
210+
})
211+
}
212+
213+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
214+
self.name.write_to(buf)?;
215+
self.version.write_to(buf)
216+
}
217+
}
218+
219+
#[derive(Clone, Default, Debug)]
220+
pub struct Registry {
221+
pub name: String,
222+
pub marker: String,
223+
}
224+
225+
impl Serializable for Registry {
226+
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
227+
Ok(Registry {
228+
name: Serializable::read_from(buf)?,
229+
marker: "".to_string(), // not in ModList
230+
})
231+
}
232+
233+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
234+
self.name.write_to(buf)?;
235+
self.marker.write_to(buf)
236+
}
237+
}
238+
239+
#[derive(Debug)]
240+
pub enum FmlHandshake {
241+
ModList {
242+
mod_names: LenPrefixed<VarInt, String>,
243+
channels: LenPrefixed<VarInt, Channel>,
244+
registries: LenPrefixed<VarInt, Registry>,
245+
},
246+
247+
ModListReply {
248+
mod_names: LenPrefixed<VarInt, String>,
249+
channels: LenPrefixed<VarInt, Channel>,
250+
registries: LenPrefixed<VarInt, Registry>,
251+
},
252+
253+
ServerRegistry {
254+
name: String,
255+
snapshot_present: bool,
256+
snapshot: Vec<u8>,
257+
},
258+
259+
ConfigurationData {
260+
filename: String,
261+
contents: Vec<u8>,
262+
},
263+
264+
Acknowledgement,
265+
}
266+
267+
impl FmlHandshake {
268+
pub fn packet_by_id<R: io::Read>(id: i32, buf: &mut R) -> Result<Self, Error> {
269+
Ok(match id {
270+
1 => FmlHandshake::ModList {
271+
mod_names: Serializable::read_from(buf)?,
272+
channels: Serializable::read_from(buf)?,
273+
registries: Serializable::read_from(buf)?,
274+
},
275+
3 => FmlHandshake::ServerRegistry {
276+
name: Serializable::read_from(buf)?,
277+
snapshot_present: Serializable::read_from(buf)?,
278+
snapshot: Serializable::read_from(buf)?,
279+
},
280+
4 => FmlHandshake::ConfigurationData {
281+
filename: Serializable::read_from(buf)?,
282+
contents: Serializable::read_from(buf)?,
283+
},
284+
_ => unimplemented!(),
285+
})
286+
}
287+
}
288+
289+
impl Serializable for FmlHandshake {
290+
fn read_from<R: io::Read>(_buf: &mut R) -> Result<Self, Error> {
291+
unimplemented!()
292+
}
293+
294+
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
295+
match self {
296+
FmlHandshake::ModListReply {
297+
mod_names,
298+
channels,
299+
registries,
300+
} => {
301+
VarInt(2).write_to(buf)?;
302+
mod_names.write_to(buf)?;
303+
channels.write_to(buf)?;
304+
registries.write_to(buf)
305+
}
306+
FmlHandshake::Acknowledgement => VarInt(99).write_to(buf),
307+
_ => unimplemented!(),
308+
}
309+
}
310+
}
311+
}

protocol/src/protocol/mod.rs

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,7 @@ pub enum Direction {
971971

972972
/// The protocol has multiple 'sub-protocols' or states which control which
973973
/// packet an id points to.
974-
#[derive(Clone, Copy, Debug)]
974+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
975975
pub enum State {
976976
Handshaking,
977977
Play,
@@ -1036,7 +1036,7 @@ pub struct Conn {
10361036

10371037
cipher: Option<Aes128Cfb>,
10381038

1039-
compression_threshold: i32,
1039+
pub compression_threshold: i32,
10401040
}
10411041

10421042
impl Conn {
@@ -1099,17 +1099,91 @@ impl Conn {
10991099
}
11001100
self.write_all(&buf)?;
11011101

1102-
Result::Ok(())
1102+
Ok(())
11031103
}
11041104

1105-
pub fn read_packet(&mut self) -> Result<packet::Packet, Error> {
1106-
let len = VarInt::read_from(self)?.0 as usize;
1105+
pub fn write_plugin_message(&mut self, channel: &str, data: &[u8]) -> Result<(), Error> {
1106+
if is_network_debug() {
1107+
debug!(
1108+
"Sending plugin message: channel={}, data={:?}",
1109+
channel, data
1110+
);
1111+
}
1112+
debug_assert!(self.state == State::Play);
1113+
if self.protocol_version >= 47 {
1114+
self.write_packet(packet::play::serverbound::PluginMessageServerbound {
1115+
channel: channel.to_string(),
1116+
data: data.to_vec(),
1117+
})?;
1118+
} else {
1119+
self.write_packet(packet::play::serverbound::PluginMessageServerbound_i16 {
1120+
channel: channel.to_string(),
1121+
data: LenPrefixedBytes::<VarShort>::new(data.to_vec()),
1122+
})?;
1123+
}
1124+
1125+
Ok(())
1126+
}
1127+
1128+
pub fn write_fmlhs_plugin_message(&mut self, msg: &forge::FmlHs) -> Result<(), Error> {
1129+
let mut buf: Vec<u8> = vec![];
1130+
msg.write_to(&mut buf)?;
1131+
1132+
self.write_plugin_message("FML|HS", &buf)
1133+
}
1134+
1135+
pub fn write_login_plugin_response(
1136+
&mut self,
1137+
message_id: VarInt,
1138+
successful: bool,
1139+
data: &[u8],
1140+
) -> Result<(), Error> {
1141+
if is_network_debug() {
1142+
debug!(
1143+
"Sending login plugin message: message_id={:?}, successful={:?}, data={:?}",
1144+
message_id, successful, data,
1145+
);
1146+
}
1147+
debug_assert!(self.state == State::Login);
1148+
self.write_packet(packet::login::serverbound::LoginPluginResponse {
1149+
message_id,
1150+
successful,
1151+
data: data.to_vec(),
1152+
})
1153+
}
1154+
1155+
pub fn write_fml2_handshake_plugin_message(
1156+
&mut self,
1157+
message_id: VarInt,
1158+
msg: Option<&forge::fml2::FmlHandshake>,
1159+
) -> Result<(), Error> {
1160+
if let Some(msg) = msg {
1161+
let mut inner_buf: Vec<u8> = vec![];
1162+
msg.write_to(&mut inner_buf)?;
1163+
1164+
let mut outer_buf: Vec<u8> = vec![];
1165+
"fml:handshake".to_string().write_to(&mut outer_buf)?;
1166+
VarInt(inner_buf.len() as i32).write_to(&mut outer_buf)?;
1167+
inner_buf.write_to(&mut outer_buf)?;
1168+
1169+
self.write_login_plugin_response(message_id, true, &outer_buf)
1170+
} else {
1171+
unimplemented!() // successful: false, no payload
1172+
}
1173+
}
1174+
1175+
#[allow(clippy::type_complexity)]
1176+
pub fn read_raw_packet_from<R: io::Read>(
1177+
buf: &mut R,
1178+
compression_threshold: i32,
1179+
) -> Result<(i32, Box<io::Cursor<Vec<u8>>>), Error> {
1180+
let len = VarInt::read_from(buf)?.0 as usize;
11071181
let mut ibuf = vec![0; len];
1108-
self.read_exact(&mut ibuf)?;
1182+
buf.read_exact(&mut ibuf)?;
11091183

11101184
let mut buf = io::Cursor::new(ibuf);
11111185

1112-
if self.compression_threshold >= 0 {
1186+
if compression_threshold >= 0 {
11131187
let uncompressed_size = VarInt::read_from(&mut buf)?.0;
11141188
if uncompressed_size != 0 {
11151189
let mut new = Vec::with_capacity(uncompressed_size as usize);
@@ -1120,7 +1194,7 @@ impl Conn {
11201194
if is_network_debug() {
11211195
debug!(
11221196
"Decompressed threshold={} len={} uncompressed_size={} to {} bytes",
1123-
self.compression_threshold,
1197+
compression_threshold,
11241198
len,
11251199
uncompressed_size,
11261200
new.len()
@@ -1131,6 +1205,13 @@ impl Conn {
11311205
}
11321206
let id = VarInt::read_from(&mut buf)?.0;
11331207

1208+
Ok((id, Box::new(buf)))
1209+
}
1210+
1211+
pub fn read_packet(&mut self) -> Result<packet::Packet, Error> {
1212+
let compression_threshold = self.compression_threshold;
1213+
let (id, mut buf) = Conn::read_raw_packet_from(self, compression_threshold)?;
1214+
11341215
let dir = match self.direction {
11351216
Direction::Clientbound => Direction::Serverbound,
11361217
Direction::Serverbound => Direction::Clientbound,
@@ -1224,6 +1305,7 @@ impl Conn {
12241305

12251306
// For modded servers, get the list of Forge mods installed
12261307
let mut forge_mods: std::vec::Vec<crate::protocol::forge::ForgeMod> = vec![];
1308+
let mut fml_network_version: Option<i64> = None;
12271309
if let Some(modinfo) = val.get("modinfo") {
12281310
if let Some(modinfo_type) = modinfo.get("type") {
12291311
if modinfo_type == "FML" {
@@ -1240,6 +1322,7 @@ impl Conn {
12401322
.push(crate::protocol::forge::ForgeMod { modid, version });
12411323
}
12421324
}
1325+
fml_network_version = Some(1);
12431326
}
12441327
}
12451328
} else {
@@ -1267,6 +1350,13 @@ impl Conn {
12671350
}
12681351
}
12691352
}
1353+
fml_network_version = Some(
1354+
forge_data
1355+
.get("fmlNetworkVersion")
1356+
.unwrap()
1357+
.as_i64()
1358+
.unwrap(),
1359+
);
12701360
}
12711361

12721362
Ok((
@@ -1301,6 +1391,7 @@ impl Conn {
13011391
.and_then(Value::as_str)
13021392
.map(|v| v.to_owned()),
13031393
forge_mods,
1394+
fml_network_version,
13041395
},
13051396
ping,
13061397
))
@@ -1352,6 +1443,7 @@ pub struct Status {
13521443
pub description: format::Component,
13531444
pub favicon: Option<String>,
13541445
pub forge_mods: Vec<crate::protocol::forge::ForgeMod>,
1446+
pub fml_network_version: Option<i64>,
13551447
}
13561448

13571449
#[derive(Debug)]

protocol/src/protocol/packet.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3135,6 +3135,10 @@ pub enum CommandProperty {
31353135
EntitySummon,
31363136
Dimension,
31373137
UUID,
3138+
ForgeModId,
3139+
ForgeEnum {
3140+
cls: String,
3141+
},
31383142
}
31393143

31403144
impl Serializable for CommandNode {
@@ -3264,6 +3268,10 @@ impl Serializable for CommandNode {
32643268
"minecraft:entity_summon" => CommandProperty::EntitySummon,
32653269
"minecraft:dimension" => CommandProperty::Dimension,
32663270
"minecraft:uuid" => CommandProperty::UUID,
3271+
"forge:modid" => CommandProperty::ForgeModId,
3272+
"forge:enum" => CommandProperty::ForgeEnum {
3273+
cls: Serializable::read_from(buf)?,
3274+
},
32673275
_ => panic!("unsupported command node parser {}", parse),
32683276
})
32693277
} else {

src/main.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub struct Game {
9191

9292
impl Game {
9393
pub fn connect_to(&mut self, address: &str) {
94-
let (protocol_version, forge_mods) =
94+
let (protocol_version, forge_mods, fml_network_version) =
9595
match protocol::Conn::new(&address, self.default_protocol_version)
9696
.and_then(|conn| conn.do_status())
9797
{
@@ -100,14 +100,18 @@ impl Game {
100100
"Detected server protocol version {}",
101101
res.0.version.protocol
102102
);
103-
(res.0.version.protocol, res.0.forge_mods)
103+
(
104+
res.0.version.protocol,
105+
res.0.forge_mods,
106+
res.0.fml_network_version,
107+
)
104108
}
105109
Err(err) => {
106110
warn!(
107111
"Error pinging server {} to get protocol version: {:?}, defaulting to {}",
108112
address, err, self.default_protocol_version
109113
);
110-
(self.default_protocol_version, vec![])
114+
(self.default_protocol_version, vec![], None)
111115
}
112116
};
113117

@@ -127,6 +131,7 @@ impl Game {
127131
&address,
128132
protocol_version,
129133
forge_mods,
134+
fml_network_version,
130135
))
131136
.unwrap();
132137
});

0 commit comments

Comments
 (0)