Finish basic server and client impl
This commit is contained in:
parent
a082210fa9
commit
178cc201c1
4 changed files with 213 additions and 57 deletions
|
@ -15,7 +15,7 @@ class NbtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool readBoolean(CompoundTag tag, String name) {
|
static bool readBoolean(CompoundTag tag, String name) {
|
||||||
if (tag.contains(name)) {
|
if (tag.containsKey(name)) {
|
||||||
return tag.get(name)!.asByte() == 1 ? true : false;
|
return tag.get(name)!.asByte() == 1 ? true : false;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -30,7 +30,7 @@ class NbtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Vector2d readVector2d(CompoundTag tag, String name) {
|
static Vector2d readVector2d(CompoundTag tag, String name) {
|
||||||
if (tag.contains(name)) {
|
if (tag.containsKey(name)) {
|
||||||
ListTag lst = tag.get(name)! as ListTag;
|
ListTag lst = tag.get(name)! as ListTag;
|
||||||
return Vector2d(X: lst.get(0).asDouble(), Z: lst.get(1).asDouble());
|
return Vector2d(X: lst.get(0).asDouble(), Z: lst.get(1).asDouble());
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,7 +46,7 @@ class NbtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Vector2i readVector2i(CompoundTag tag, String name) {
|
static Vector2i readVector2i(CompoundTag tag, String name) {
|
||||||
if (tag.contains(name)) {
|
if (tag.containsKey(name)) {
|
||||||
ListTag lst = tag.get(name)! as ListTag;
|
ListTag lst = tag.get(name)! as ListTag;
|
||||||
return Vector2i(X: lst.get(0).asInt(), Z: lst.get(1).asInt());
|
return Vector2i(X: lst.get(0).asInt(), Z: lst.get(1).asInt());
|
||||||
} else {
|
} else {
|
||||||
|
@ -63,7 +63,7 @@ class NbtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Vector3d readVector3d(CompoundTag tag, String name) {
|
static Vector3d readVector3d(CompoundTag tag, String name) {
|
||||||
if (tag.contains(name)) {
|
if (tag.containsKey(name)) {
|
||||||
ListTag lst = tag.get(name)! as ListTag;
|
ListTag lst = tag.get(name)! as ListTag;
|
||||||
return Vector3d(
|
return Vector3d(
|
||||||
X: lst.get(0).asDouble(),
|
X: lst.get(0).asDouble(),
|
||||||
|
@ -83,7 +83,7 @@ class NbtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Vector3i readVector3i(CompoundTag tag, String name) {
|
static Vector3i readVector3i(CompoundTag tag, String name) {
|
||||||
if (tag.contains(name)) {
|
if (tag.containsKey(name)) {
|
||||||
ListTag lst = tag.get(name)! as ListTag;
|
ListTag lst = tag.get(name)! as ListTag;
|
||||||
return Vector3i(
|
return Vector3i(
|
||||||
X: lst.get(0).asInt(), Y: lst.get(1).asInt(), Z: lst.get(2).asInt());
|
X: lst.get(0).asInt(), Y: lst.get(1).asInt(), Z: lst.get(2).asInt());
|
||||||
|
@ -111,7 +111,7 @@ class NbtUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
static NbtUUID readUUID(CompoundTag tag, String name) {
|
static NbtUUID readUUID(CompoundTag tag, String name) {
|
||||||
if (!tag.contains(name)) {
|
if (!tag.containsKey(name)) {
|
||||||
return NbtUUID.ZERO;
|
return NbtUUID.ZERO;
|
||||||
} else {
|
} else {
|
||||||
return _uuidFromIntArray(tag.get(name)!.asIntArray());
|
return _uuidFromIntArray(tag.get(name)!.asIntArray());
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:libac_flutter/nbt/Stream.dart';
|
import 'package:libac_flutter/nbt/Stream.dart';
|
||||||
import 'package:libac_flutter/nbt/Tag.dart';
|
import 'package:libac_flutter/nbt/Tag.dart';
|
||||||
|
|
||||||
class CompoundTag extends Tag {
|
class CompoundTag extends Tag implements Map<String, Tag> {
|
||||||
late final Map<String, Tag> value = {};
|
late final Map<String, Tag> value = {};
|
||||||
|
|
||||||
CompoundTag();
|
CompoundTag();
|
||||||
|
@ -48,12 +48,8 @@ class CompoundTag extends Tag {
|
||||||
tag.setKey(name);
|
tag.setKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(String name) {
|
|
||||||
return value.containsKey(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Tag? get(String name) {
|
Tag? get(String name) {
|
||||||
if (contains(name)) {
|
if (containsKey(name)) {
|
||||||
return value[name] as Tag;
|
return value[name] as Tag;
|
||||||
} else {
|
} else {
|
||||||
// Does not exist!
|
// Does not exist!
|
||||||
|
@ -61,10 +57,6 @@ class CompoundTag extends Tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(String name) {
|
|
||||||
value.remove(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TagType getTagType() {
|
TagType getTagType() {
|
||||||
return TagType.Compound;
|
return TagType.Compound;
|
||||||
|
@ -103,4 +95,99 @@ class CompoundTag extends Tag {
|
||||||
}
|
}
|
||||||
builder.append("\n${"".padLeft(indent - 1, '\t')}}");
|
builder.append("\n${"".padLeft(indent - 1, '\t')}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tag? operator [](Object? key) {
|
||||||
|
return value[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void operator []=(String key, Tag value) {
|
||||||
|
this.value[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addAll(Map<String, Tag> other) {
|
||||||
|
value.addAll(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addEntries(Iterable<MapEntry<String, Tag>> newEntries) {
|
||||||
|
value.addEntries(newEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<RK, RV> cast<RK, RV>() {
|
||||||
|
return value.cast();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clear() {
|
||||||
|
value.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool containsKey(Object? key) {
|
||||||
|
return value.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool containsValue(Object? value) {
|
||||||
|
return this.value.containsValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<MapEntry<String, Tag>> get entries => value.entries;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void forEach(void Function(String key, Tag value) action) {
|
||||||
|
value.forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEmpty => value.isEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isNotEmpty => value.isNotEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<String> get keys => value.keys;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get length => value.length;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<K2, V2> map<K2, V2>(
|
||||||
|
MapEntry<K2, V2> Function(String key, Tag value) convert) {
|
||||||
|
return value.map(convert);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tag putIfAbsent(String key, Tag Function() ifAbsent) {
|
||||||
|
return this.value.putIfAbsent(key, ifAbsent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tag? remove(Object? key) {
|
||||||
|
return value.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeWhere(bool Function(String key, Tag value) test) {
|
||||||
|
value.removeWhere(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tag update(String key, Tag Function(Tag value) update,
|
||||||
|
{Tag Function()? ifAbsent}) {
|
||||||
|
return value.update(key, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateAll(Tag Function(String key, Tag value) update) {
|
||||||
|
value.updateAll(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<Tag> get values => value.values;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,17 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:libac_flutter/nbt/NbtIo.dart';
|
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/Tag.dart';
|
||||||
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
import 'package:libac_flutter/nbt/impl/CompoundTag.dart';
|
||||||
import 'package:libac_flutter/nbt/impl/StringTag.dart';
|
import 'package:libac_flutter/nbt/impl/StringTag.dart';
|
||||||
|
|
||||||
class PacketServer {
|
class PacketServer {
|
||||||
|
static ServerSocket? socket;
|
||||||
static Future<void> start() async {
|
static Future<void> start() async {
|
||||||
final ServerSocket socket =
|
socket = await ServerSocket.bind(InternetAddress.anyIPv4, 25306);
|
||||||
await ServerSocket.bind(InternetAddress.anyIPv4, 25306);
|
|
||||||
print("Server now listening on port 25306");
|
print("Server now listening on port 25306");
|
||||||
|
|
||||||
await for (var sock in socket) {
|
await for (var sock in socket!) {
|
||||||
print(
|
print(
|
||||||
"New connection from ${sock.remoteAddress.address}:${sock.remotePort}");
|
"New connection from ${sock.remoteAddress.address}:${sock.remotePort}");
|
||||||
|
|
||||||
|
@ -21,11 +20,16 @@ class PacketServer {
|
||||||
S2CStatusResponse response = S2CStatusResponse();
|
S2CStatusResponse response = S2CStatusResponse();
|
||||||
try {
|
try {
|
||||||
CompoundTag tag = await NbtIo.readFromStream(data);
|
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) {
|
} catch (E) {
|
||||||
response.status = false;
|
|
||||||
response.reason = "Malformed request packet";
|
response.reason = "Malformed request packet";
|
||||||
|
|
||||||
sock.write(
|
sock.add(
|
||||||
await NbtIo.writeToStream(response.encodeTag() as CompoundTag));
|
await NbtIo.writeToStream(response.encodeTag() as CompoundTag));
|
||||||
}
|
}
|
||||||
}, onDone: () {
|
}, onDone: () {
|
||||||
|
@ -37,14 +41,99 @@ class PacketServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
abstract class IPacket with NbtEncodable, JsonEncodable {
|
||||||
String getChannelID();
|
String getChannelID();
|
||||||
|
|
||||||
// This function handles the packet
|
// This function handles the packet
|
||||||
Future<void> handlePacket();
|
Future<PacketResponse> handlePacket();
|
||||||
NetworkDirection direction();
|
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 {
|
class PacketRegistry {
|
||||||
Map<String, IPacket Function()> _registry = {};
|
Map<String, IPacket Function()> _registry = {};
|
||||||
static PacketRegistry _inst = PacketRegistry._();
|
static PacketRegistry _inst = PacketRegistry._();
|
||||||
|
@ -55,6 +144,8 @@ class PacketRegistry {
|
||||||
return _inst;
|
return _inst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get count => _registry.length;
|
||||||
|
|
||||||
void register(IPacket packet, IPacket Function() packetResolver) {
|
void register(IPacket packet, IPacket Function() packetResolver) {
|
||||||
_registry[packet.getChannelID()] = packetResolver;
|
_registry[packet.getChannelID()] = packetResolver;
|
||||||
}
|
}
|
||||||
|
@ -71,6 +162,12 @@ class PacketRegistry {
|
||||||
register(S2CStatusResponse(), () {
|
register(S2CStatusResponse(), () {
|
||||||
return S2CStatusResponse();
|
return S2CStatusResponse();
|
||||||
});
|
});
|
||||||
|
register(C2SRequestPacket(), () {
|
||||||
|
return C2SRequestPacket();
|
||||||
|
});
|
||||||
|
register(StopServerPacket(), () {
|
||||||
|
return StopServerPacket();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,9 +176,7 @@ enum NetworkDirection { ClientToServer, ServerToClient }
|
||||||
enum PacketOperation { Encode, Decode }
|
enum PacketOperation { Encode, Decode }
|
||||||
|
|
||||||
class S2CStatusResponse implements IPacket {
|
class S2CStatusResponse implements IPacket {
|
||||||
bool status = false;
|
|
||||||
String reason = "";
|
String reason = "";
|
||||||
late IPacket payload;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
NetworkDirection direction() {
|
NetworkDirection direction() {
|
||||||
|
@ -94,11 +189,9 @@ class S2CStatusResponse implements IPacket {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> handlePacket() async {
|
Future<PacketResponse> handlePacket() async {
|
||||||
// No handling is required for this packet type
|
// No handling is required for this packet type
|
||||||
if (status) {
|
return PacketResponse.nil;
|
||||||
payload.handlePacket();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -109,15 +202,7 @@ class S2CStatusResponse implements IPacket {
|
||||||
@override
|
@override
|
||||||
void decodeTag(Tag encoded) {
|
void decodeTag(Tag encoded) {
|
||||||
CompoundTag ct = encoded as CompoundTag;
|
CompoundTag ct = encoded as CompoundTag;
|
||||||
status = NbtUtils.readBoolean(ct, "status");
|
|
||||||
reason = ct.get("reason")!.asString();
|
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
|
@override
|
||||||
|
@ -128,39 +213,22 @@ class S2CStatusResponse implements IPacket {
|
||||||
@override
|
@override
|
||||||
Tag encodeTag() {
|
Tag encodeTag() {
|
||||||
CompoundTag tag = CompoundTag();
|
CompoundTag tag = CompoundTag();
|
||||||
NbtUtils.writeBoolean(tag, "status", status);
|
|
||||||
tag.put("reason", StringTag.valueOf(reason));
|
tag.put("reason", StringTag.valueOf(reason));
|
||||||
if (status) {
|
|
||||||
tag.put("pc", StringTag.valueOf(payload.getChannelID()));
|
|
||||||
tag.put("payload", payload.encodeTag());
|
|
||||||
}
|
|
||||||
|
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void fromJson(Map<String, dynamic> params) {
|
void fromJson(Map<String, dynamic> params) {
|
||||||
status = params["status"] as bool;
|
|
||||||
reason = params["reason"] as String;
|
reason = params["reason"] as String;
|
||||||
|
|
||||||
if (status) {
|
|
||||||
String channel = params['pc'] as String;
|
|
||||||
payload = PacketRegistry().getPacket(channel);
|
|
||||||
payload.fromJson(params['payload']);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
Map<String, dynamic> map = {
|
Map<String, dynamic> map = {
|
||||||
"status": status,
|
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (status) {
|
|
||||||
map.addAll({"pc": payload.getChannelID(), "payload": payload.toJson()});
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,8 +282,9 @@ class C2SRequestPacket implements IPacket {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> handlePacket() async {
|
Future<PacketResponse> handlePacket() async {
|
||||||
// This has no internal handling
|
// This has no internal handling
|
||||||
|
return payload.handlePacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -17,7 +17,7 @@ void main() {
|
||||||
CompoundTag tag =
|
CompoundTag tag =
|
||||||
await NbtIo.read("${Directory.current.path}/test/hello_world.nbt");
|
await NbtIo.read("${Directory.current.path}/test/hello_world.nbt");
|
||||||
expect(tag.getKey(), "hello world");
|
expect(tag.getKey(), "hello world");
|
||||||
expect(tag.contains("name"), true);
|
expect(tag.containsKey("name"), true);
|
||||||
|
|
||||||
expect(tag.get("name")!.asString(), "Bananrama");
|
expect(tag.get("name")!.asString(), "Bananrama");
|
||||||
});
|
});
|
||||||
|
@ -37,7 +37,7 @@ void main() {
|
||||||
CompoundTag tag =
|
CompoundTag tag =
|
||||||
await NbtIo.read("${Directory.current.path}/build/hello_world.nbt");
|
await NbtIo.read("${Directory.current.path}/build/hello_world.nbt");
|
||||||
expect(tag.getKey(), "hello world");
|
expect(tag.getKey(), "hello world");
|
||||||
expect(tag.contains("name"), true);
|
expect(tag.containsKey("name"), true);
|
||||||
|
|
||||||
expect(tag.get("name")!.asString(), "Bananrama");
|
expect(tag.get("name")!.asString(), "Bananrama");
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue