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 }