import 'dart:convert'; import 'dart:io'; import 'package:libac_flutter/nbt/NbtIo.dart'; import 'package:libac_flutter/nbt/NbtUtils.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 Future start() async { final ServerSocket 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}"); sock.listen((data) async { S2CStatusResponse response = S2CStatusResponse(); try { CompoundTag tag = await NbtIo.readFromStream(data); } catch (E) { response.status = false; response.reason = "Malformed request packet"; sock.write( await NbtIo.writeToStream(response.encodeTag() as CompoundTag)); } }, onDone: () { sock.close(); }, onError: () { sock.close(); }); } } } abstract class IPacket with NbtEncodable, JsonEncodable { String getChannelID(); // This function handles the packet Future handlePacket(); NetworkDirection direction(); } class PacketRegistry { Map _registry = {}; static PacketRegistry _inst = PacketRegistry._(); PacketRegistry._(); factory PacketRegistry() { return _inst; } 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(); }); } } enum NetworkDirection { ClientToServer, ServerToClient } enum PacketOperation { Encode, Decode } class S2CStatusResponse implements IPacket { bool status = false; String reason = ""; late IPacket payload; @override NetworkDirection direction() { return NetworkDirection.ServerToClient; } @override String getChannelID() { return "StatusResponse"; } @override Future handlePacket() async { // No handling is required for this packet type if (status) { payload.handlePacket(); } } @override void decodeJson(String encoded) { fromJson(json.decode(encoded)); } @override void decodeTag(Tag encoded) { CompoundTag ct = encoded as CompoundTag; status = NbtUtils.readBoolean(ct, "status"); reason = ct.get("reason")!.asString(); if (ct.contains("payload")) { String channel = ct.get("pc")!.asString(); payload = PacketRegistry().getPacket(channel); payload.decodeTag(ct.get("payload")!); } } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); NbtUtils.writeBoolean(tag, "status", status); tag.put("reason", StringTag.valueOf(reason)); if (status) { tag.put("pc", StringTag.valueOf(payload.getChannelID())); tag.put("payload", payload.encodeTag()); } return tag; } @override void fromJson(Map params) { status = params["status"] as bool; reason = params["reason"] as String; if (status) { String channel = params['pc'] as String; payload = PacketRegistry().getPacket(channel); payload.fromJson(params['payload']); } } @override Map toJson() { Map map = { "status": status, "reason": reason, }; if (status) { map.addAll({"pc": payload.getChannelID(), "payload": payload.toJson()}); } 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 } @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); }