Hello, I need to implement the Minecraft Protocol (Server List Ping - wiki.vg) in Swift, but I'm unsure about how to use SwiftNIO and where to begin.
Thanks in advance.
Hello, I need to implement the Minecraft Protocol (Server List Ping - wiki.vg) in Swift, but I'm unsure about how to use SwiftNIO and where to begin.
Thanks in advance.
It looks like the basic structure of the protocol is defined here. The document there looks like its laid out pretty close to the way you want it, actually, starting at the smallest protocol atoms and moving up to message types.
My recommended flow would be to go through this document, top to bottom, and start writing translations into methods on ByteBuffer
. Typically this is how NIO protocols are built.
For example, I'd begin with the Data Types
. Each of those should have an appropriate read
and write
method on ByteBuffer
. Many of those will simply delegate to other ByteBuffer
methods, but that's totally fine: your goal is that you want a bunch of methods that amount to saying "give me the next thing of this kind". An example here is in swift-nio-ssh, where a bunch of the core SSH data types got the appropriate methods.
Easy ones might be readMinecraftBoolean
:
extension ByteBuffer {
mutating func readMinecraftBoolean() throws -> Bool? {
return try self.readInteger(as: UInt8.self).map {
switch $0 {
case 1: return true
case 0: return false
default: throw InvalidMinecraftBoolean($0)
}
}
}
More challenging ones might be readVarint
, in part because you need to put the cursor back after you've done your parse:
extension ByteBuffer {
mutating func readMinecraftVarint() throws -> Int32? {
var copy = self
var shift = 0
var base = Int32(0)
while let nextByte = copy.readInteger(as: UInt8.self) {
value |= (nextByte & 0x7F) << shift
if nextByte & 0x80 != 0 {
// Complete parse, store the cursor and return.
self = copy
return value
}
position &+= 7
if position >= 32 { throw InvalidMinecraftVarint() }
}
// Ran out of bytes, return nil.
return nil
}
}
Once you have those, you can start writing message types. These should be struct
s that lay out the logical parts of a message. You can then write read
/write
extension methods on ByteBuffer that produce those things, by reading and writing their constituent elements. Again, swift-nio-ssh is a useful example.
With that done, you're almost ready to go. Define all the message types you understand in a big enum
and then write a Parser
type. This type accepts ByteBuffer
s and concatenates them together, and then has a nextMessage()
method that will return the next message, assuming you have enough bytes.
You can wrap that Parser
into a NIOByteToMessageDecoder
, and then put that into a Channel
, and you'll be able to read messages from the remote peer! You can use your write
methods to test you got the encode correct, and you can then use them to build up a Serializer
(which is a MessageToByteEncoder
) to send messages the other way.
I’ve got a project called Delta Client where I’m writing a Minecraft client in Swift. Feel free to check out the code or join the Discord if you need any specific help with your implementation! I didn’t use NIO, but most of the parsing logic should still be similar.
Thank you, I will consider both of your posts and later in case I will write updates regarding my project
FWIW, if you decide you don't need the additional complexity of Swift-NIO, there's still a handful of fine BSD Socket API abstractions available for Swift.
Any example and link to GitHub?
The socket library that I use is called FlyingSocks (part of the FlyingFox package). If I recall correctly, I tried using NIO at one point, but found that for implementing the Minecraft protocol NIO really didn’t provide many useful advantages and just over complicated things a bit.
For Apple platforms one of the choices is a modern alternative to sockets: Network framework.
And there are more.
I think on Apple platforms, in particular on mobile, you are supposed to use Network.framework going forward (or something layering on top, like SwiftNIO on Darwin w/ the transport services package).
Like SwiftNIO it is built around async I/O. The API is a little weird, but workable, I've wrote an intro article covering it.