import 'dart:convert'; import 'package:libac_flutter/nbt/NbtUtils.dart'; import 'package:libac_flutter/nbt/Stream.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 'package:libac_flutter/packets/packets.dart'; import 'package:libac_flutter/utils/Hashing.dart'; import 'package:libac_flutter/utils/uuid/NbtUUID.dart'; import 'package:libac_flutter/utils/uuid/UUID.dart'; import 'package:servermanager/statemachine.dart'; import 'package:servermanager/structs/SessionData.dart'; import 'package:servermanager/structs/settings.dart'; class ClientPackets { static void register() { PacketRegistry reg = PacketRegistry(); reg.register(C2SLoginPacket(), () { return C2SLoginPacket(); }); reg.register(S2CLoginReply(), () { return S2CLoginReply(); }); reg.register(C2SRequestSettingsPacket(), () { return C2SRequestSettingsPacket(); }); reg.register(C2SUploadSettingsPacket(), () { return C2SUploadSettingsPacket(); }); } } class C2SLoginPacket implements IPacket { String username = ""; String password = ""; @override void decodeJson(String params) { fromJson(json.decode(params)); } @override void decodeTag(Tag tag) { CompoundTag ct = tag.asCompoundTag(); username = ct.get("username")!.asString(); password = ct.get("password")!.asString(); } @override NetworkDirection direction() { return NetworkDirection.ClientToServer; } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); tag.put("username", StringTag.valueOf(username)); tag.put("password", StringTag.valueOf(Hashing.sha256Hash(password))); return tag; } @override void fromJson(Map js) { username = js['username'] as String; password = js['password'] as String; } @override String getChannelID() { return "Login"; } @override Future handleServerPacket() async { S2CResponse response = S2CResponse(); S2CLoginReply loginReply = S2CLoginReply(); // Attempt to log in. Settings settings = Settings(); if (settings.serverLoginCreds.username == username && Hashing.sha256Hash(settings.serverLoginCreds.password) == password) { settings.remoteLoginToken = UUID.generate(4); loginReply.valid = true; loginReply.token = settings.remoteLoginToken; } else { //print( // "Login failure\n${settings.serverLoginCreds.username}:${username}\n${Hashing.sha256Hash(settings.serverLoginCreds.password)}:${password}"); loginReply.valid = false; } response.contents = loginReply.encodeTag().asCompoundTag(); return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag()); } @override Map toJson() { return {"username": username, "password": Hashing.sha256Hash(password)}; } @override Future handleClientPacket() async {} } class S2CLoginReply implements IPacket { bool valid = false; UUID token = UUID.ZERO; @override void decodeJson(String params) { fromJson(json.decode(params)); } @override void decodeTag(Tag tag) { print("Decoding S2C LoginReply"); StringBuilder sb = StringBuilder(); Tag.writeStringifiedNamedTag(tag, sb, 0); print(sb); CompoundTag ct = tag as CompoundTag; if (ct.containsKey("valid")) valid = NbtUtils.readBoolean(ct, "valid"); if (ct.containsKey("token")) { token = NbtUtils.readUUID(ct, "token").toUUID(); } } @override NetworkDirection direction() { return NetworkDirection.ServerToClient; } @override String encodeJson() { return json.encode(toJson()); } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); NbtUtils.writeBoolean(tag, "valid", valid); NbtUtils.writeUUID(tag, "token", NbtUUID.fromUUID(token)); return tag; } @override void fromJson(Map js) { valid = js['valid'] as bool; token = UUID.parse(js['token'] as String); } @override String getChannelID() { return "LoginReply"; } @override Future handleClientPacket() async { // Handle login finalization related stuff Settings settings = Settings(); settings.remoteLoginToken = token; } @override Future handleServerPacket() async { return PacketResponse.nil; // We only operate on the client } @override Map toJson() { return {"valid": valid, "token": token.toString()}; } } class C2SRequestSettingsPacket implements IPacket { CompoundTag serverSettings = CompoundTag(); @override void decodeJson(String params) { throw UnsupportedError("Json is unsupported by LibACNBT at this time"); } @override void decodeTag(Tag tag) { CompoundTag ct = tag as CompoundTag; serverSettings = ct.get("settings")!.asCompoundTag(); } @override NetworkDirection direction() { return NetworkDirection.ClientToServer; } @override String encodeJson() { return ""; } @override Tag encodeTag() { CompoundTag ct = CompoundTag(); ct.put("settings", Settings().serialize()); return ct; } @override void fromJson(Map js) {} @override String getChannelID() { return "C2SRequestSettings"; } @override Future handleClientPacket() async { Settings settings = Settings(); settings.deserialize(serverSettings); settings.server = false; } @override Future handleServerPacket() async { S2CResponse response = S2CResponse(); Settings settings = Settings(); serverSettings = settings.serialize(); response.contents = encodeTag().asCompoundTag(); return PacketResponse(replyDataTag: response.encodeTag().asCompoundTag()); } @override Map toJson() { return {}; } } class C2SUploadSettingsPacket implements IPacket { CompoundTag srvSettings = CompoundTag(); @override void decodeJson(String params) {} @override void decodeTag(Tag tag) { srvSettings = tag.asCompoundTag().get("settings")!.asCompoundTag(); } @override NetworkDirection direction() { return NetworkDirection.ClientToServer; } @override String encodeJson() { return ""; } @override Tag encodeTag() { CompoundTag tag = CompoundTag(); tag.put("settings", Settings().serialize()); return tag; } @override void fromJson(Map js) {} @override String getChannelID() { return "C2SUploadSettings"; } @override Future handleClientPacket() async { // No client response or handling needed } @override Future handleServerPacket() async { Settings settings = Settings(); settings.deserialize(srvSettings); settings.Write(); // Check if server is running, if not, stop immediately // If server is running, schedule restart for 1 minute and send a alert to all players, then perform stop or restart depending on if running in Pterodactyl Compatibility mode SessionData.shutdownMessage = "Server wrapper updated. Restart required."; SessionData.timer.apply(60); SessionData.CURRENT_INTERVAL = WarnIntervals.NONE; if (settings.subsys.currentState == States.Inactive) { SessionData.shutdownPending = true; // Stop packet server PacketServer.socket!.close(); } return PacketResponse.nil; } @override Map toJson() { return {}; } }