307 lines
6.6 KiB
Dart
307 lines
6.6 KiB
Dart
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<void> 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}");
|
|
|
|
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) {
|
|
response.reason = "Malformed request packet";
|
|
|
|
sock.add(
|
|
await NbtIo.writeToStream(response.encodeTag() as CompoundTag));
|
|
}
|
|
}, onDone: () {
|
|
sock.close();
|
|
}, onError: () {
|
|
sock.close();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
class PacketClient {
|
|
Socket? socket;
|
|
bool connected = false;
|
|
|
|
PacketClient(String IPAddress) {
|
|
startConnect(IPAddress);
|
|
}
|
|
|
|
Future<void> startConnect(String IPAddress) async {
|
|
try {
|
|
socket = await Socket.connect(IPAddress, 25306);
|
|
connected = true;
|
|
} catch (E) {
|
|
connected = false;
|
|
socket = null;
|
|
}
|
|
}
|
|
|
|
Future<CompoundTag> send(IPacket packet) async {
|
|
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<PacketResponse> 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<String, dynamic> js) {}
|
|
|
|
@override
|
|
String getChannelID() {
|
|
return "StopServer";
|
|
}
|
|
|
|
@override
|
|
Future<PacketResponse> handlePacket() async {
|
|
PacketServer.socket!.close();
|
|
|
|
return PacketResponse(replyDataTag: CompoundTag());
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
class PacketResponse {
|
|
static final nil = PacketResponse(replyDataTag: CompoundTag());
|
|
|
|
PacketResponse({required this.replyDataTag});
|
|
|
|
CompoundTag replyDataTag = CompoundTag();
|
|
}
|
|
|
|
class PacketRegistry {
|
|
Map<String, IPacket Function()> _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<PacketResponse> 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<String, dynamic> params) {
|
|
reason = params["reason"] as String;
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() {
|
|
Map<String, dynamic> 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<String, dynamic> params) {
|
|
String cap = params['cap'] as String;
|
|
payload = PacketRegistry().getPacket(cap);
|
|
payload.fromJson(params['payload']);
|
|
}
|
|
|
|
@override
|
|
String getChannelID() {
|
|
return "C2SRequest";
|
|
}
|
|
|
|
@override
|
|
Future<PacketResponse> handlePacket() async {
|
|
// This has no internal handling
|
|
return payload.handlePacket();
|
|
}
|
|
|
|
@override
|
|
Map<String, dynamic> toJson() {
|
|
return {"cap": payload.getChannelID(), "payload": payload.toJson()};
|
|
}
|
|
}
|
|
|
|
mixin JsonEncodable {
|
|
String encodeJson();
|
|
void decodeJson(String params);
|
|
|
|
Map<String, dynamic> toJson();
|
|
void fromJson(Map<String, dynamic> js);
|
|
}
|
|
|
|
mixin NbtEncodable {
|
|
Tag encodeTag();
|
|
void decodeTag(Tag tag);
|
|
}
|