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'; class PacketServer { static ServerSocket? socket; 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 { S2CStatusResponse response = S2CStatusResponse(); try { CompoundTag tag = await NbtIo.readFromStream(data); C2SRequestPacket request = C2SRequestPacket(); request.decodeTag(tag); PacketResponse reply = await request.handlePacket(); // Server uses NBT to communicate sock.add(await NbtIo.writeToStream(reply.replyDataTag)); } catch (E, stack) { response.reason = "Malformed request packet"; sock.add( await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); } }, onDone: () { sock.close(); }, onError: () { sock.close(); }); } catch (E) { sock.close(); } } } } class PacketClient { Socket? socket; bool connected = false; PacketClient(String IPAddress) { startConnect(IPAddress); } Future startConnect(String IPAddress) async { try { socket = await Socket.connect(IPAddress, 25306); connected = true; } catch (E, stack) { connected = false; socket = null; print(stack); } } Future send(IPacket packet) async { if (!connected) { return CompoundTag(); } socket!.add(await NbtIo.writeToStream(packet.encodeTag().asCompoundTag())); CompoundTag ct = CompoundTag(); socket!.listen((data) async { ct = await NbtIo.readFromStream(data); }); return ct; } void close() { socket!.close(); connected = false; } } abstract class IPacket with NbtEncodable, JsonEncodable { String getChannelID(); // This function handles the packet Future handlePacket(); 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 handlePacket() async { PacketServer.socket!.close(); return PacketResponse(replyDataTag: CompoundTag()); } @override Map toJson() { return {}; } } class PacketResponse { static final nil = PacketResponse(replyDataTag: CompoundTag()); PacketResponse({required this.replyDataTag}); CompoundTag replyDataTag = CompoundTag(); } class PacketRegistry { Map _registry = {}; static PacketRegistry _inst = PacketRegistry._(); PacketRegistry._(); 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(S2CStatusResponse(), () { return S2CStatusResponse(); }); register(C2SRequestPacket(), () { return C2SRequestPacket(); }); register(StopServerPacket(), () { return StopServerPacket(); }); } } enum NetworkDirection { ClientToServer, ServerToClient } enum PacketOperation { Encode, Decode } class S2CStatusResponse implements IPacket { String reason = ""; @override NetworkDirection direction() { return NetworkDirection.ServerToClient; } @override String getChannelID() { return "StatusResponse"; } @override Future handlePacket() async { // No handling is required for this packet type return PacketResponse.nil; } @override void decodeJson(String encoded) { fromJson(json.decode(encoded)); } @override void decodeTag(Tag encoded) { CompoundTag ct = encoded as CompoundTag; reason = ct.get("reason")!.asString(); } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); tag.put("reason", StringTag.valueOf(reason)); return tag; } @override void fromJson(Map params) { reason = params["reason"] as String; } @override Map toJson() { Map map = { "reason": reason, }; return map; } } 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 handlePacket() async { // This has no internal handling return payload.handlePacket(); } @override Map toJson() { return {"cap": payload.getChannelID(), "payload": payload.toJson()}; } } mixin JsonEncodable { String encodeJson(); void decodeJson(String params); Map toJson(); void fromJson(Map js); } mixin NbtEncodable { Tag encodeTag(); void decodeTag(Tag tag); }