TCPClient for Minecraft

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 structs 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 ByteBuffers 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.

12 Likes

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.

1 Like

Thank you, I will consider both of your posts and later in case I will write updates regarding my project

1 Like

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.

2 Likes

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.

1 Like

And there are more.

1 Like

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.