1 /* 2 * Copyright (c) 2017-2020 sel-project 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in all 12 * copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 * 22 */ 23 /** 24 * Copyright: 2017-2020 sel-project 25 * License: MIT 26 * Authors: Kripth 27 * Source: $(HTTP github.com/sel-project/sel-client/sel/client/bedrock.d, sel/client/bedrock.d) 28 */ 29 module sel.client.bedrock; 30 31 import std.conv : to, ConvException; 32 import std.datetime : Duration; 33 import std.datetime.stopwatch : StopWatch; 34 import std.random : uniform; 35 import std.socket : Socket, UdpSocket, SocketOptionLevel, SocketOption, Address; 36 import std.string : split; 37 38 import sel.client.client : isSupported, Client; 39 import sel.client.util : Server, IHandler; 40 import sel.net : Stream, RaknetStream; 41 42 debug import std.stdio : writeln; 43 44 import RaknetTypes = sul.protocol.raknet8.types; 45 import Control = sul.protocol.raknet8.control; 46 import Encapsulated = sul.protocol.raknet8.encapsulated; 47 import Unconnected = sul.protocol.raknet8.unconnected; 48 49 enum __magic = cast(ubyte[16])x"00 FF FF 00 FE FE FE FE FD FD FD FD 12 34 56 78"; 50 51 enum type(uint protocol) = protocol < 120 ? "pocket" : "bedrock"; 52 53 class BedrockClient(uint __protocol) : Client if(isSupported!(type!__protocol, __protocol)) { 54 55 public static string randomUsername() { 56 enum char[] pool = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ".dup; 57 char[] ret = new char[uniform!"[]"(1, 15)]; 58 foreach(i, ref c; ret) { 59 c = pool[uniform(0, (i==0 || i==ret.length-1 ? $-1 : $))]; 60 } 61 return ret.idup; 62 } 63 64 mixin("import Play = sul.protocol." ~ type!__protocol ~ to!string(__protocol) ~ ".play;"); 65 mixin("import Types = sul.protocol." ~ type!__protocol ~ to!string(__protocol) ~ ".types;"); 66 67 alias Clientbound = FilterPackets!("CLIENTBOUND", Play.Packets); 68 alias Serverbound = FilterPackets!("SERVERBOUND", Play.Packets); 69 70 public this(string name) { 71 super(name); 72 } 73 74 public this() { 75 this(randomUsername()); 76 } 77 78 public override pure nothrow @property @safe @nogc ushort defaultPort() { 79 return ushort(19132); 80 } 81 82 protected override Server pingImpl(Address address, string ip, ushort port, Duration timeout) { 83 StopWatch timer; 84 timer.start(); 85 auto spl = this.rawPingImpl(address, ip, port, timeout).split(";"); 86 if(spl.length >= 6 && spl[0] == "MCPE") { 87 uint latency; 88 timer.peek.split!"msecs"(latency); 89 try { 90 return Server(spl[1], to!uint(spl[2]), to!int(spl[4]), to!int(spl[5]), latency); 91 } catch(ConvException) {} 92 } 93 return Server.init; 94 } 95 96 protected override string rawPingImpl(Address address, string ip, ushort port, Duration timeout) { 97 Socket socket = new UdpSocket(address.addressFamily); 98 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 99 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, timeout); 100 socket.sendTo(new Unconnected.Ping(0, __magic, 0).encode(), address); 101 ubyte[] buffer = new ubyte[512]; 102 if(socket.receiveFrom(buffer, address) > 0 && buffer[0] == Unconnected.Pong.ID) { 103 return Unconnected.Pong.fromBuffer(buffer).status; 104 } else { 105 return ""; 106 } 107 } 108 109 protected override Stream connectImpl(Address address, string ip, ushort port, Duration timeout, IHandler handler) { 110 ubyte[] buffer = new ubyte[2048]; 111 ptrdiff_t recv; 112 Socket socket = new UdpSocket(address.addressFamily); 113 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, timeout); 114 foreach(mtu ; [2000, 1700, 1400, 1000, 500, 500]) { 115 socket.sendTo(new Unconnected.OpenConnectionRequest1(__magic, 8, new ubyte[mtu]).encode(), address); 116 } 117 recv = socket.receiveFrom(buffer, address); 118 if(recv > 0 && buffer[0] == Unconnected.OpenConnectionReply1.ID) { 119 auto reply1 = Unconnected.OpenConnectionReply1.fromBuffer(buffer); 120 if(reply1.mtuLength <= 2000 && reply1.mtuLength >= 500) { 121 buffer = new ubyte[reply1.mtuLength + 100]; 122 socket.sendTo(new Unconnected.OpenConnectionRequest2(__magic, RaknetTypes.Address.init, reply1.mtuLength, 0).encode(), address); 123 do { 124 recv = socket.receiveFrom(buffer, address); 125 } while(recv > 0 && buffer[0] != Unconnected.OpenConnectionReply2.ID); 126 if(recv > 0) { 127 auto reply2 = Unconnected.OpenConnectionReply2.fromBuffer(buffer); 128 writeln(reply2); 129 // start encapsulation 130 auto stream = new RaknetStream(socket, address, reply1.mtuLength); 131 stream.send(new Encapsulated.ClientConnect(0, 0).encode()); 132 buffer = stream.receive(); 133 if(buffer.length && buffer[0] == Encapsulated.ServerHandshake.ID) { 134 auto sh = Encapsulated.ServerHandshake.fromBuffer(buffer); 135 stream.send(new Encapsulated.ClientHandshake(sh.clientAddress, sh.systemAddresses, sh.pingId, 0).encode()); 136 // start new tick-based thread 137 { 138 bool connected = true; 139 while(connected) { 140 //TODO receive from other thread 141 //TODO receive from socket (and do ack/nack, split management, handlers call) 142 //TODO flush packets (queued from send method) 143 //TODO wait ~50 ms 144 } 145 } 146 } 147 } 148 } 149 } 150 return null; 151 } 152 153 } 154 155 private struct FilterPackets(string property, E...) { 156 @disable this(); 157 alias F = FilterPacketsImpl!(property, 0, E); 158 mixin((){ 159 string ret; 160 foreach(i, P; F) { 161 ret ~= "alias " ~ P.stringof ~ "=F[" ~ to!string(i) ~ "];"; 162 } 163 return ret; 164 }()); 165 } 166 167 private template FilterPacketsImpl(string property, size_t index, E...) { 168 static if(index < E.length) { 169 static if(mixin("E[index]." ~ property)) { 170 alias FilterPacketsImpl = FilterPacketsImpl!(property, index+1, E); 171 } else { 172 alias FilterPacketsImpl = FilterPacketsImpl!(property, index, E[0..index], E[index+1..$]); 173 } 174 } else { 175 alias FilterPacketsImpl = E; 176 } 177 }