Skip to content
1 change: 1 addition & 0 deletions building/libs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<git name="hxdiscord_rpc" url="https://github.com/CodenameCrew/cne-hxdiscord_rpc" skipDeps="true" />
<lib name="funkin-modchart" version="1.2.4" skipDeps="true" />
<lib name="hxvlc" version="1.9.3" skipDeps="true" />
<lib name="hxWebSockets" />

<!-- Documentation and other features -->
<git name="away3d" url="https://github.com/CodenameCrew/away3d" />
Expand Down
2 changes: 2 additions & 0 deletions project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@
<haxelib name="flixel" />
<haxelib name="flixel-addons" />

<haxelib name="hxWebSockets" />

<haxelib name="hxvlc" if="VIDEO_CUTSCENES" />
<haxelib name="away3d" if="THREE_D_SUPPORT" />
<haxelib name="format" />
Expand Down
76 changes: 76 additions & 0 deletions source/funkin/backend/system/net/FunkinPacket.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package funkin.backend.system.net;

import flixel.util.typeLimit.OneOfTwo;

import haxe.io.Bytes;
import haxe.io.BytesOutput;

class FunkinPacket {
public var status:Int = -1;
public var head:String;
public var fields:Map<String, String> = new Map<String, String>();
public var body:OneOfTwo<String, Bytes>;

public function new(_head:String, ?_body:OneOfTwo<String, Bytes> = "", ?_status:Int = -1) {
this.head = _head.trim();
this.body = (_body is String) ? _body.trim() : _body;
this.status = _status;
}

public function set(name:String, value:String):FunkinPacket { fields.set(name, value); return this; }
inline public function get(name:String):String { return fields.get(name); }
inline public function exists(name:String):Bool { return fields.exists(name); }
inline public function remove(name:String):Bool { return fields.remove(name); }
inline public function keys():Iterator<String> { return fields.keys(); }

public function toString(?includeBody:Bool = true):String {
var str:String = '';
if (head.length > 0) str += '$head\r\n';
for (key in keys()) str += '$key: ${get(key)}\r\n';
if (!includeBody) return str;
if (body is String) str += body;
else str += (cast body : Bytes).toString();
return str;
}
public function toBytes():Bytes {
var bytes:BytesOutput = new BytesOutput();
bytes.writeString(toString(false)); // Absolute Cinema, thanks AbstractAndrew for the Revolutionary Idea 🔥🔥
if (body is String) bytes.writeString(body);
else if (body is Bytes) bytes.write(body);
return bytes.getBytes();
}

public static function fromBytes(bytes:Bytes):Null<FunkinPacket> {
var status:Int = -1;

var header_length:Int = -1;
var header:String = "";

var body_is_string:Bool = false;
var body_length:Int = -1;
var body:OneOfTwo<String, Bytes> = null;

try {
var offset:Int = 0;
status = bytes.getInt32(0); offset += 4;
header_length = bytes.getInt32(offset); offset += 4;
header = bytes.getString(offset, header_length); offset += header_length;
body_is_string = (bytes.get(offset) == 1); offset += 1;
body_length = bytes.getInt32(offset); offset += 4;
if (body_is_string) body = bytes.getString(offset, body_length);
else body = bytes.sub(offset, body_length);
} catch(e) {
FlxG.log.error('FunkinPacket.fromBytes() failed to parse packet: $e');
return null;
}
var packet:FunkinPacket = new FunkinPacket(null, body, status);
for (line in header.split("\r\n")) {
var data = line.split(": ");
if (data.length < 2) continue;
var key:String = data.shift().trim();
var value:String = data.shift().trim();
packet.set(key, value);
}
return packet;
}
}
129 changes: 129 additions & 0 deletions source/funkin/backend/system/net/FunkinSocket.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package funkin.backend.system.net;

#if sys
import sys.net.Host;
import sys.net.Socket as SysSocket;
import haxe.io.Bytes;

@:keep
class FunkinSocket implements IFlxDestroyable {
public var socket:SysSocket = new SysSocket();

public var metrics:Metrics = new Metrics();

public var FAST_SEND(default, set):Bool = true;
private function set_FAST_SEND(value:Bool):Bool {
FAST_SEND = value;
socket.setFastSend(value);
return value;
}
public var BLOCKING(default, set):Bool = false;
private function set_BLOCKING(value:Bool):Bool {
BLOCKING = value;
socket.setBlocking(value);
return value;
}

public var host:Host;
public var port:Int;

public function new(?_host:String = "127.0.0.1", ?_port:Int = 5000) {
FAST_SEND = true;
BLOCKING = false;
this.host = new Host(_host);
this.port = _port;
}

// Reading Area
public function readAll():Null<Bytes> {
try {
var bytes = this.socket.input.readAll();
if (bytes == null) return null;
metrics.updateBytesReceived(bytes.length);
return bytes;
} catch(e) { }
return null;
}
public function readLine():Null<String> {
try {
var bytes = this.socket.input.readLine();
if (bytes == null) return null;
metrics.updateBytesReceived(bytes.length);
return bytes;
} catch(e) { }
return null;
}
public function read(nBytes:Int):Null<Bytes> {
try {
var bytes = this.socket.input.read(nBytes);
if (bytes == null) return null;
metrics.updateBytesReceived(bytes.length);
return bytes;
} catch(e) { }
return null;
}
public function readBytes(bytes:Bytes):Int {
try {
var length = this.socket.input.readBytes(bytes, 0, bytes.length);
metrics.updateBytesReceived(length);
return length;
} catch(e) { }
return 0;
}

// Writing Area
public function prepare(nbytes:Int):Void { socket.output.prepare(nbytes); }
public function write(bytes:Bytes):Bool {
try {
this.socket.output.write(bytes);
metrics.updateBytesSent(bytes.length);
return true;
} catch (e) { }
return false;
}
public function writeString(str:String):Bool {
try {
this.socket.output.writeString(str);
metrics.updateBytesSent(Bytes.ofString(str).length);
return true;
} catch(e) { }
return false;
}

public function bind(?expectingConnections:Int = 1):FunkinSocket {
Logs.traceColored([
Logs.logText('[FunkinSocket] ', BLUE),
Logs.logText('Binding to ', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN),
]);
socket.bind(host, port);
socket.listen(expectingConnections);
return this;
}

public function connect():FunkinSocket {
Logs.traceColored([
Logs.logText('[FunkinSocket] ', BLUE),
Logs.logText('Connecting to ', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN),
]);
socket.connect(host, port);
return this;
}

public function close() {
try {
if (socket != null) socket.close();
Logs.traceColored([
Logs.logText('[FunkinSocket] ', BLUE),
Logs.logText('Closing socket from ', NONE), Logs.logText(host.toString(), YELLOW), Logs.logText(':', NONE), Logs.logText(Std.string(port), CYAN),
]);
} catch(e) {
Logs.traceColored([
Logs.logText('[FunkinSocket] ', BLUE),
Logs.logText('Failed to close socket: ${e}', NONE),
]);
}
}

public function destroy() { close(); }
}
#end
Loading