Roll my own rcon implementation due to problems with other implementation
This commit is contained in:
parent
74fe37d7d8
commit
15aae5c3d7
5 changed files with 96 additions and 412 deletions
95
lib/utils/rcon/Rcon.dart
Normal file
95
lib/utils/rcon/Rcon.dart
Normal file
|
@ -0,0 +1,95 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
class RCONPacket {
|
||||
int id;
|
||||
int type;
|
||||
String body;
|
||||
|
||||
RCONPacket(this.id, this.type, this.body);
|
||||
|
||||
List<int> toBytes() {
|
||||
List<int> bodyBytes = utf8.encode(body);
|
||||
int size = 10 + bodyBytes.length;
|
||||
List<int> packet = [];
|
||||
packet.addAll(_intToBytes(size));
|
||||
packet.addAll(_intToBytes(id));
|
||||
packet.addAll(_intToBytes(type));
|
||||
packet.addAll(bodyBytes);
|
||||
packet.addAll([0, 0]);
|
||||
return packet;
|
||||
}
|
||||
|
||||
static RCONPacket fromBytes(List<int> bytes) {
|
||||
int size = _bytesToInt(bytes.sublist(0, 4));
|
||||
int id = _bytesToInt(bytes.sublist(4, 8));
|
||||
int type = _bytesToInt(bytes.sublist(8, 12));
|
||||
String body =
|
||||
utf8.decode(bytes.sublist(12, size + 2).sublist(0, size - 10));
|
||||
return RCONPacket(id, type, body);
|
||||
}
|
||||
|
||||
static List<int> _intToBytes(int value) {
|
||||
return [
|
||||
value & 0xFF,
|
||||
(value >> 8) & 0xFF,
|
||||
(value >> 16) & 0xFF,
|
||||
(value >> 24) & 0xFF
|
||||
];
|
||||
}
|
||||
|
||||
static int _bytesToInt(List<int> bytes) {
|
||||
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
|
||||
}
|
||||
}
|
||||
|
||||
class RCONClient {
|
||||
String host;
|
||||
int port;
|
||||
String password;
|
||||
late Socket _socket;
|
||||
late int _requestId;
|
||||
|
||||
RCONClient(this.host, this.port, this.password) {
|
||||
_requestId = 0;
|
||||
}
|
||||
|
||||
Future<void> connect() async {
|
||||
_socket = await Socket.connect(host, port);
|
||||
if (await _authenticate()) {
|
||||
print('Authenticated successfully');
|
||||
} else {
|
||||
print('Authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _authenticate() async {
|
||||
RCONPacket packet = RCONPacket(_requestId++, 3, password);
|
||||
_socket.add(packet.toBytes());
|
||||
await _socket.flush();
|
||||
RCONPacket response = await _readPacket();
|
||||
return response.id == packet.id && response.type == 2;
|
||||
}
|
||||
|
||||
Future<RCONPacket> _readPacket() async {
|
||||
List<int> sizeBytes = await _socket.first;
|
||||
int size = RCONPacket._bytesToInt(sizeBytes);
|
||||
List<int> dataBytes = [];
|
||||
while (dataBytes.length < size) {
|
||||
dataBytes.addAll(await _socket.first);
|
||||
}
|
||||
return RCONPacket.fromBytes(sizeBytes + dataBytes);
|
||||
}
|
||||
|
||||
Future<String> sendCommand(String command) async {
|
||||
RCONPacket packet = RCONPacket(_requestId++, 2, command);
|
||||
_socket.add(packet.toBytes());
|
||||
await _socket.flush();
|
||||
RCONPacket response = await _readPacket();
|
||||
return response.body;
|
||||
}
|
||||
|
||||
void close() {
|
||||
_socket.close();
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:libac_dart/utils/rcon/rcon_helpers.dart';
|
||||
import 'package:libac_dart/utils/rcon/rcon_vars.dart';
|
||||
|
||||
/// Creates and stores a socket connected to the RCON server
|
||||
/// with the given host (IP/FQDN) and port. Port defaults to
|
||||
/// 25575 if no port is specified.
|
||||
Future<void> createSocket(String host, {int port = 25575}) async {
|
||||
// Creates the socket by connecting the socket to the specified
|
||||
// host and port.
|
||||
rconSck = await Socket.connect(host, port);
|
||||
}
|
||||
|
||||
/// Closes the socket to the RCON server. Returns a bool that
|
||||
/// specified whether the socket was successfully destroyed.
|
||||
bool close() {
|
||||
// Checks to ensure that the RCON socket exists.
|
||||
if (rconSck == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Destroys the socket, which also closes the connection.
|
||||
rconSck!.destroy();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Send a message with the given message ID and message payload.
|
||||
/// Returns a boolean that specifies if the command was successful.
|
||||
bool sendMsg(int msgID, String payload) {
|
||||
// Ensures that the RCON socket exists.
|
||||
if (rconSck == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Message length is the payload length + 10 to account
|
||||
// for the headers and suffix.
|
||||
int msgLen = 10 + payload.length;
|
||||
|
||||
// Creates the full RCON message.
|
||||
Uint8List fullMsg = cM(msgLen, msgID, payload);
|
||||
|
||||
// Add the RCON message to the socket stream.
|
||||
rconSck!.add(fullMsg);
|
||||
print("mc_rcon: sent payload ($fullMsg) on socket");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Log in to the RCON server using the given socket and password.
|
||||
/// Returns a boolean that specifies if the command was successful.
|
||||
bool login(String password) {
|
||||
// Sends an RCON message with request ID = 3 (authenticate)
|
||||
// with the password as the payload.
|
||||
return sendMsg(3, password);
|
||||
}
|
||||
|
||||
/// Send the provided command to the RCON server using the
|
||||
/// Returns a boolean that specifies if the command was successful.
|
||||
bool sendCommand(String command) {
|
||||
// Sends an RCON message with request ID = 2 (command)
|
||||
// with the String command as the payload.
|
||||
return sendMsg(2, command);
|
||||
}
|
||||
|
||||
/// Starts listening on the socket for packets sent by the RCON
|
||||
/// server. Returns a boolean that specifies if the socket has
|
||||
/// started listening. Note: onData must accept a List<int> and
|
||||
/// a String as the only parameters.
|
||||
bool listen(Function onData) {
|
||||
// Checks to ensure that the RCON socket exists.
|
||||
if (rconSck == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Starts listening on the RCON socket.
|
||||
// Calls the first handler if we receive data, calls onError
|
||||
// if there is an error on the stream, calls onDone when the
|
||||
// client or the server ends the connection.
|
||||
rconSck!.listen(
|
||||
(Uint8List data) {
|
||||
pSR(data, onData);
|
||||
},
|
||||
onError: (error) {
|
||||
print('mc_rcon: Error with the connection to the server: $error');
|
||||
rconSck!.destroy();
|
||||
},
|
||||
onDone: () {
|
||||
print('mc_rcon: The server has ended the connection.');
|
||||
rconSck!.destroy();
|
||||
},
|
||||
cancelOnError: false,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'rcon_vars.dart';
|
||||
|
||||
/// Returns a Uint8Lit that contains only 4 integers starting
|
||||
/// from original[start]. Start defaults to 0 if not set. If
|
||||
/// 4 integers cannot be copied from the list (e.g. list length
|
||||
/// is less than 4 or start is too large to copy 4 integers), a
|
||||
/// list of [-1, -1, -1, -1] will be returned. Returns 0 in place
|
||||
/// of any integer if that location is empty in the source list.
|
||||
Uint8List _u8LCopy4(Uint8List original, {int start = 0}) {
|
||||
// Ensure that the Uint8List is at least len 4 and that
|
||||
// start is list.length-4 or less
|
||||
if (original.length < 4 || start + 3 > original.length - 1) {
|
||||
// If not, return a list that is all -1.
|
||||
Uint8List errorList = Uint8List(4)
|
||||
..[0] = -1
|
||||
..[1] = -1
|
||||
..[2] = -1
|
||||
..[3] = -1;
|
||||
|
||||
return errorList;
|
||||
}
|
||||
|
||||
// Creates a new length 4 Uint8List and sets each of the
|
||||
// values to a value from the original list.
|
||||
Uint8List copiedUint8s = Uint8List(4)
|
||||
..[0] = original[start]
|
||||
..[1] = original[start + 1]
|
||||
..[2] = original[start + 2]
|
||||
..[3] = original[start + 3];
|
||||
|
||||
return copiedUint8s;
|
||||
}
|
||||
|
||||
/// Returns a Uint8Lit that contains only 4 integers starting
|
||||
/// from original[start]. Start defaults to 0 if not set. If
|
||||
/// 4 integers cannot be copied from the list (e.g. list length
|
||||
/// is less than 4 or start is too large to copy 4 integers), a
|
||||
/// list of [-1, -1, -1, -1] will be returned. Returns 0 in place
|
||||
/// of any integer if that location is empty in the source list.
|
||||
Uint8List u8LCopy4(Uint8List original, {int start = 0}) {
|
||||
return _u8LCopy4(original, start: start);
|
||||
}
|
||||
|
||||
/// Returns a little Endian int32 interpreted from 4 Uint8s
|
||||
/// retrieved from data (Uint8List) starting at an index of
|
||||
/// start (int). The Uint8list length must be at least 4 and
|
||||
/// start must be data.length - 4 or smaller. Returns -1
|
||||
/// if processing has failed.
|
||||
int _processToInt32(Uint8List data, int start) {
|
||||
// Checks to ensure that the Uint8List is longer than 4
|
||||
// and that the starting index is valid to parse out a
|
||||
// 32-bit integer.
|
||||
if (data.length < 4 || start > (data.length - 4)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Copies the 4 uint8s we need to parse the int32.
|
||||
Uint8List copiedUint8s = _u8LCopy4(data, start: start);
|
||||
|
||||
// I don't know what this does. But it gives me access
|
||||
// to the list and the ability to parse out a 32-bit int.
|
||||
var blob = ByteData.sublistView(copiedUint8s);
|
||||
|
||||
// Process out the 32-bit integer assuming little Endian
|
||||
// (which is what the RCON protocol uses).
|
||||
int processedInt32 = blob.getInt32(0, Endian.little);
|
||||
|
||||
return processedInt32;
|
||||
}
|
||||
|
||||
/// Returns a little Endian int32 interpreted from 4 Uint8s
|
||||
/// retrieved from data (Uint8List) starting at an index of
|
||||
/// start (int). The Uint8list length must be at least 4 and
|
||||
/// start must be data.length - 4 or smaller. Returns -1
|
||||
/// if processing has failed.
|
||||
int processToInt32(Uint8List data, int start) {
|
||||
return _processToInt32(data, start);
|
||||
}
|
||||
|
||||
/// Returns a List<int> that contains the 3 header integers
|
||||
/// processed from the Uint8List of data.
|
||||
List<int> _processHeaders(Uint8List data) {
|
||||
// Processes out each of the 3 header integers.
|
||||
int msgLength = _processToInt32(data, 0);
|
||||
int respReqID = _processToInt32(data, 4);
|
||||
int commandID = _processToInt32(data, 8);
|
||||
|
||||
// Creates a list made of the header integers.
|
||||
List<int> msgHeader = [msgLength, respReqID, commandID];
|
||||
|
||||
return msgHeader;
|
||||
}
|
||||
|
||||
/// Returns a List<int> that contains the 3 header integers
|
||||
/// processed from the Uint8List of data.
|
||||
// @visibleForTesting
|
||||
// List<int> processHeaders(Uint8List data) {
|
||||
// return _processHeaders(data);
|
||||
// }
|
||||
|
||||
/// Returns a boolean that represents whether the response ID
|
||||
/// represents a good packet. Good packet means response
|
||||
/// ID == original request ID. Bad auth packet means response
|
||||
/// ID == -1.
|
||||
bool _processResponseID(int respID) {
|
||||
if (respID == requestID) {
|
||||
// If the response ID == original request ID,
|
||||
// we received a good packet.
|
||||
print("mc_rcon: Good packet received.");
|
||||
return true;
|
||||
} else if (respID == -1) {
|
||||
// If the response ID == -1, we haven't authenticated
|
||||
// properly, which can mean we sent the wrong password
|
||||
// or we haven't authenticated yet.
|
||||
print(
|
||||
"mc_rcon: Bad authentication. Incorrect password or you haven't logged in yet.");
|
||||
return false;
|
||||
} else {
|
||||
// Catch-all for all other response IDs. Should never trigger.
|
||||
print("mc_rcon: Received unknown request ID.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a boolean that represents whether the response ID
|
||||
/// represents a good packet. Good packet means response
|
||||
/// ID == original request ID. Bad auth packet means response
|
||||
/// ID == -1.
|
||||
// @visibleForTesting
|
||||
// bool processResponseID(int respID) {
|
||||
// return _processResponseID(respID);
|
||||
// }
|
||||
|
||||
/// Processes the server response (represented as a Uint8List)
|
||||
/// and calls onData handler if we have received a good packet.
|
||||
void _processServerResponse(Uint8List data, Function onData) {
|
||||
// Parses out the message headers and payload.
|
||||
List<int> rconHeaders = _processHeaders(data);
|
||||
String payload = String.fromCharCodes(data, 12);
|
||||
|
||||
// Pulls out the messsage length to ensure the integrity
|
||||
// of the message and the response ID to print.
|
||||
int messageLen = rconHeaders[0];
|
||||
int responseID = rconHeaders[1];
|
||||
print("mc_rcon: Server response id: $responseID");
|
||||
|
||||
// Ensures that the data we recieved is the same
|
||||
// as what the length of the message is supposed to be.
|
||||
bool badMessage = (data.length == messageLen);
|
||||
|
||||
// Sends the headers and payload to the user handler function
|
||||
// if the response is good (we receive our own request ID).
|
||||
if (!badMessage && _processResponseID(responseID)) {
|
||||
onData(rconHeaders, payload);
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the server response (represented as a Uint8List)
|
||||
/// and calls onData handler if we have received a good packet.
|
||||
void processServerResponse(Uint8List data, Function onData) {
|
||||
return _processServerResponse(data, onData);
|
||||
}
|
||||
|
||||
/// Processes the server response (represented as a Uint8List)
|
||||
/// and calls onData handler if we have received a good packet.
|
||||
void pSR(Uint8List data, Function onData) {
|
||||
return _processServerResponse(data, onData);
|
||||
}
|
||||
|
||||
/// Sets every int in dataList to the Uint32List using a ByteData buffer.
|
||||
void _setUint32s(Uint32List int32List, List<int> dataList) {
|
||||
// Views the buffer of the Uint32list.
|
||||
ByteData bd = ByteData.view(int32List.buffer);
|
||||
|
||||
// Used to offset the bytes assigned to the ByteData.
|
||||
// Otherwise we will overwrite the already written bytes.
|
||||
int bdByteOffset = 0;
|
||||
|
||||
// Loops through every int data and assigns it as a little
|
||||
// Endian Uint32 to the Uint32list's ByteData buffer.
|
||||
for (int data in dataList) {
|
||||
bd.setUint32(bdByteOffset, data, Endian.little);
|
||||
bdByteOffset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets every int in dataList to the Uint32List using a ByteData buffer.
|
||||
// @visibleForTesting
|
||||
// void setUint32s(Uint32List int32List, List<int> dataList) {
|
||||
// return _setUint32s(int32List, dataList);
|
||||
// }
|
||||
|
||||
/// Returns a Uint8List that represents the header of
|
||||
/// the RCON message. Assembles the header with the
|
||||
/// specified length and message ID (i.e. type of message).
|
||||
Uint8List _assembleHeader(int payloadLen, int msgID, [int? overrideReqID]) {
|
||||
// Creates a new, length 3, Uint32List.
|
||||
Uint32List headerAs32 = Uint32List(3);
|
||||
|
||||
// Sets the three Uint32s to the proper header integers.
|
||||
_setUint32s(headerAs32, [
|
||||
payloadLen,
|
||||
overrideReqID != null ? overrideReqID : requestID,
|
||||
msgID,
|
||||
]);
|
||||
|
||||
// Transforms the header Uint32List into a Uint8List
|
||||
// for transmission.
|
||||
Uint8List header = headerAs32.buffer.asUint8List();
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
/// Returns a Uint8List that represents the header of
|
||||
/// the RCON message. Assembles the header with the
|
||||
/// specified length and message ID (i.e. type of message).
|
||||
// @visibleForTesting
|
||||
// Uint8List assembleHeader(int payloadLen, int msgID) {
|
||||
// return _assembleHeader(payloadLen, msgID);
|
||||
// }
|
||||
|
||||
/// Returns a Uint8List that represents the suffix of
|
||||
/// the RCON message. The suffix is two NULLs in ASCII,
|
||||
/// one to end the payload, and one to end the message.
|
||||
Uint8List _assembleSuffix() {
|
||||
// Creates a new, length 2, Uint8List.
|
||||
Uint8List suffix = Uint8List(2);
|
||||
|
||||
// Views the buffer of the list so we can add to the list.
|
||||
ByteData suffixBD = ByteData.view(suffix.buffer);
|
||||
|
||||
// Adds the two NULLs to the Uint8List.
|
||||
suffixBD.setUint8(0, 0);
|
||||
suffixBD.setUint8(1, 0);
|
||||
|
||||
return suffix;
|
||||
}
|
||||
|
||||
/// Returns a Uint8List that represents the suffix of
|
||||
/// the RCON message. The suffix is two NULLs in ASCII,
|
||||
/// one to end the payload, and one to end the message.
|
||||
// @visibleForTesting
|
||||
// Uint8List assembleSuffix() {
|
||||
// return _assembleSuffix();
|
||||
// }
|
||||
|
||||
/// Assembles a list of Uint8Lists into one Uint8List.
|
||||
Uint8List _assembleUint8Lists(List<Uint8List> msgParts) {
|
||||
// Creates a new BytesBuilder to assemble the ints.
|
||||
BytesBuilder bBuilder = BytesBuilder();
|
||||
|
||||
// Adds every list of Uint8s to the BB.
|
||||
// This automatically compiles the list of ints.
|
||||
for (Uint8List part in msgParts) {
|
||||
bBuilder.add(part);
|
||||
}
|
||||
|
||||
// Returns the BB's data as a Uint8list.
|
||||
return bBuilder.toBytes();
|
||||
}
|
||||
|
||||
/// Assembles a list of Uint8Lists into one Uint8List.
|
||||
// @visibleForTesting
|
||||
// Uint8List assembleUint8Lists(List<Uint8List> msgParts) {
|
||||
// return _assembleUint8Lists(msgParts);
|
||||
// }
|
||||
|
||||
/// Returns the whole RCON message as a Uint8List. Requires the
|
||||
/// message length, message ID, and the payload.
|
||||
Uint8List _createMessage(int msgLen, int msgID, String payload,
|
||||
[int? overrideReqID]) {
|
||||
// Example login data: (u__ refers to the type of unsigned integer)
|
||||
// 0d00 0000 | dec0 ad0b | 0300 0000 | 3132 3300 00
|
||||
// len u32 | pID u32 | cmdID u32 | payload/password u8
|
||||
|
||||
// Creates the 3 parts of the message using their respective
|
||||
// constructors (2 are self-written, one built into Uint8List).
|
||||
Uint8List header = _assembleHeader(msgLen, msgID, overrideReqID);
|
||||
Uint8List msgAsIntList = Uint8List.fromList(payload.codeUnits);
|
||||
Uint8List suffix = _assembleSuffix();
|
||||
|
||||
// Assembles the 3 parts into 1 Uint8List to return;
|
||||
Uint8List fullMsg = _assembleUint8Lists([header, msgAsIntList, suffix]);
|
||||
|
||||
return fullMsg;
|
||||
}
|
||||
|
||||
/// Returns the whole RCON message as a Uint8List. Requires the
|
||||
/// message length, message ID, and the payload.
|
||||
Uint8List createMessage(int msgLen, int msgID, String payload,
|
||||
[int? overrideReqID]) {
|
||||
return _createMessage(msgLen, msgID, payload, overrideReqID);
|
||||
}
|
||||
|
||||
/// Returns the whole RCON message as a Uint8List. Requires the
|
||||
/// message length, message ID, and the payload.
|
||||
Uint8List cM(int msgLen, int msgID, String payload) {
|
||||
return _createMessage(msgLen, msgID, payload);
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
/// The socket that is used to communicate with the RCON server.
|
||||
/// Generated when createSocket() is run.
|
||||
Socket? rconSck;
|
||||
|
||||
/// The randomly generated request ID that is sent with every
|
||||
/// message to the RCON server. Used to ensure that the commands
|
||||
/// sent to and received from the server are ours, and not another
|
||||
/// user's.
|
||||
int requestID = Random().nextInt(2147483647);
|
|
@ -1,6 +1,6 @@
|
|||
name: libac_dart
|
||||
description: "Aria's Creations code library"
|
||||
version: 1.0.26
|
||||
version: 1.0.27
|
||||
homepage: "https://zontreck.com"
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue