import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:libac_flutter/nbt/NbtIo.dart'; import 'package:libac_flutter/nbt/Tag.dart'; import 'package:libac_flutter/nbt/impl/CompoundTag.dart'; import 'package:libac_flutter/nbt/impl/StringTag.dart'; import '../nbt/Stream.dart'; class PacketServer { static ServerSocket? socket; static bool shouldRestart = true; static Future start() async { socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306); print("Server now listening on port 25306"); await for (var sock in socket!) { print( "New connection from ${sock.remoteAddress.address}:${sock.remotePort}"); try { sock.listen((data) async { S2CResponse response = S2CResponse(); try { CompoundTag tag = await NbtIo.readFromStream(data); StringBuilder builder = StringBuilder(); Tag.writeStringifiedNamedTag(tag, builder, 0); print("Request from client: \n${builder}"); C2SRequestPacket request = C2SRequestPacket(); request.decodeTag(tag); PacketResponse reply = await request.handleServerPacket(); // Server uses NBT to communicate sock.add(await NbtIo.writeToStream(reply.replyDataTag)); } catch (E, stack) { response.contents .put("error", StringTag.valueOf("Malformed request packet")); sock.add( await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); } finally { await sock.flush(); sock.close(); if (!shouldRestart) { await socket!.close(); } } }, onDone: () { sock.close(); }, onError: (E) { print("ERROR: ${E}"); sock.close(); }); } catch (E) { sock.close(); } } } } class PacketClient { Socket? socket; bool connected = false; String lastIP = ""; PacketClient(); Future startConnect(String IPAddress) async { try { socket = await Socket.connect(IPAddress, 25306); connected = true; lastIP = IPAddress; } catch (E, stack) { connected = false; socket = null; } } Future send(IPacket packet) async { if (!connected) { return S2CResponse(); } C2SRequestPacket request = C2SRequestPacket(); request.payload = packet; request.cap = packet.getChannelID(); socket!.add(await NbtIo.writeToStream(request.encodeTag().asCompoundTag())); CompoundTag ct = CompoundTag(); Completer onCompletion = Completer(); socket!.listen((data) async { CompoundTag result = await NbtIo.readFromStream(data); ct.put("result", result); }, onError: (E) { print("ERROR: ${E}"); }, onDone: () { print("Request completed"); onCompletion.complete(); }); await onCompletion.future; await close(); await startConnect(lastIP); S2CResponse reply = S2CResponse(); reply.decodeTag(ct.get("result")!.asCompoundTag()); return reply; } Future close() async { await socket!.close(); connected = false; } } abstract class IPacket with NbtEncodable, JsonEncodable { String getChannelID(); // This function handles the packet Future handleServerPacket(); Future handleClientPacket(); NetworkDirection direction(); } class StopServerPacket extends IPacket { @override void decodeJson(String params) {} @override void decodeTag(Tag tag) {} @override NetworkDirection direction() { return NetworkDirection.ClientToServer; } @override String encodeJson() { return json.encode({}); } @override Tag encodeTag() { return CompoundTag(); } @override void fromJson(Map js) {} @override String getChannelID() { return "StopServer"; } @override Future handleServerPacket() async { // We're now on the server. Handle the packet with a response to the client PacketServer.shouldRestart = false; S2CResponse response = S2CResponse(); return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag()); } @override Map toJson() { return {}; } @override Future handleClientPacket() { throw UnimplementedError(); } } class PacketResponse { static final nil = PacketResponse(replyDataTag: CompoundTag()); PacketResponse({required this.replyDataTag}); CompoundTag replyDataTag = CompoundTag(); } class PacketRegistry { Map _registry = {}; static PacketRegistry _inst = PacketRegistry._(); PacketRegistry._() { registerDefaults(); } factory PacketRegistry() { return _inst; } int get count => _registry.length; void register(IPacket packet, IPacket Function() packetResolver) { _registry[packet.getChannelID()] = packetResolver; } IPacket getPacket(String channel) { if (_registry.containsKey(channel)) { IPacket Function() callback = _registry[channel]!; return callback(); } else throw Exception("No such channel has been registered"); } void registerDefaults() { register(S2CResponse(), () { return S2CResponse(); }); register(C2SRequestPacket(), () { return C2SRequestPacket(); }); register(StopServerPacket(), () { return StopServerPacket(); }); register(C2SPing(), () { return C2SPing(); }); } } enum NetworkDirection { ClientToServer, ServerToClient } enum PacketOperation { Encode, Decode } class S2CResponse implements IPacket { CompoundTag contents = CompoundTag(); @override NetworkDirection direction() { return NetworkDirection.ServerToClient; } @override String getChannelID() { return "Response"; } @override Future handleServerPacket() async { // We can't predict handling for this type, it is a data packet response with no pre-defined structure. return PacketResponse.nil; } @override void decodeJson(String encoded) { fromJson(json.decode(encoded)); } @override void decodeTag(Tag encoded) { CompoundTag ct = encoded as CompoundTag; contents = ct.get("contents")!.asCompoundTag(); } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); tag.put("contents", contents); return tag; } @override void fromJson(Map params) {} @override Map toJson() { return {}; // Operation is not supported at this time. } @override Future handleClientPacket() async { // We haven't got anything to process. This is structured data } } class C2SRequestPacket implements IPacket { String cap = ""; // Packet channel late IPacket payload; @override void decodeJson(String encoded) { fromJson(json.decode(encoded)); } @override void decodeTag(Tag encoded) { CompoundTag tag = encoded.asCompoundTag(); String cap = tag.get("cap")!.asString(); payload = PacketRegistry().getPacket(cap); payload.decodeTag(tag.get("payload")!.asCompoundTag()); } @override NetworkDirection direction() { return NetworkDirection.ClientToServer; } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); tag.put("cap", StringTag.valueOf(payload.getChannelID())); tag.put("payload", payload.encodeTag()); return tag; } @override void fromJson(Map params) { String cap = params['cap'] as String; payload = PacketRegistry().getPacket(cap); payload.fromJson(params['payload']); } @override String getChannelID() { return "C2SRequest"; } @override Future handleServerPacket() async { // This has no internal handling return payload.handleServerPacket(); } @override Map toJson() { return {"cap": payload.getChannelID(), "payload": payload.toJson()}; } @override Future handleClientPacket() { throw UnimplementedError(); } } class C2SPing implements IPacket { String clientVersion = ""; @override void decodeJson(String params) { fromJson(json.decode(params)); } @override void decodeTag(Tag tag) { clientVersion = tag.asCompoundTag().get("version")!.asString(); } @override NetworkDirection direction() { return NetworkDirection.ClientToServer; } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); tag.put("version", StringTag.valueOf(clientVersion)); return tag; } @override void fromJson(Map js) { clientVersion = js['version'] as String; } @override String getChannelID() { return "Ping"; } @override Future handleServerPacket() async { CompoundTag tag = CompoundTag(); tag.put("pong", StringTag.valueOf(Platform.version)); S2CResponse response = S2CResponse(); response.contents = tag; PacketResponse reply = PacketResponse(replyDataTag: response.encodeTag().asCompoundTag()); return reply; } @override Map toJson() { return {"version": clientVersion}; } @override Future handleClientPacket() { throw UnimplementedError(); } } mixin JsonEncodable { String encodeJson(); void decodeJson(String params); Map toJson(); void fromJson(Map js); } mixin NbtEncodable { Tag encodeTag(); void decodeTag(Tag tag); }