From 05827d25733698a26cc0f305966e6a8a03be4684 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 25 Sep 2025 18:26:53 -0400 Subject: Game talks through CommonClient now --- apworld/client/vendor/LICENSE | 28 ++--- apworld/client/vendor/WebSocketServer.gd | 173 +++++++++++++++++++++++++++ apworld/client/vendor/uuid.gd | 195 ------------------------------- 3 files changed, 187 insertions(+), 209 deletions(-) create mode 100644 apworld/client/vendor/WebSocketServer.gd delete mode 100644 apworld/client/vendor/uuid.gd (limited to 'apworld/client/vendor') diff --git a/apworld/client/vendor/LICENSE b/apworld/client/vendor/LICENSE index 115ba15..12763b1 100644 --- a/apworld/client/vendor/LICENSE +++ b/apworld/client/vendor/LICENSE @@ -1,21 +1,21 @@ -MIT License +WebSocketServer.gd: -Copyright (c) 2023 Xavier Sellier +Copyright (c) 2014-present Godot Engine contributors. Copyright (c) 2007-2014 +Juan Linietsky, Ariel Manzur. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/apworld/client/vendor/WebSocketServer.gd b/apworld/client/vendor/WebSocketServer.gd new file mode 100644 index 0000000..2cee494 --- /dev/null +++ b/apworld/client/vendor/WebSocketServer.gd @@ -0,0 +1,173 @@ +class_name WebSocketServer +extends Node + +signal message_received(peer_id: int, message: String) +signal client_connected(peer_id: int) +signal client_disconnected(peer_id: int) + +@export var handshake_headers := PackedStringArray() +@export var supported_protocols := PackedStringArray() +@export var handshake_timout := 3000 +@export var use_tls := false +@export var tls_cert: X509Certificate +@export var tls_key: CryptoKey +@export var refuse_new_connections := false: + set(refuse): + if refuse: + pending_peers.clear() + + +class PendingPeer: + var connect_time: int + var tcp: StreamPeerTCP + var connection: StreamPeer + var ws: WebSocketPeer + + func _init(p_tcp: StreamPeerTCP) -> void: + tcp = p_tcp + connection = p_tcp + connect_time = Time.get_ticks_msec() + + +var tcp_server := TCPServer.new() +var pending_peers: Array[PendingPeer] = [] +var peers: Dictionary + + +func listen(port: int) -> int: + assert(not tcp_server.is_listening()) + return tcp_server.listen(port) + + +func stop() -> void: + tcp_server.stop() + pending_peers.clear() + peers.clear() + + +func send(peer_id: int, message: String) -> int: + var type := typeof(message) + if peer_id <= 0: + # Send to multiple peers, (zero = broadcast, negative = exclude one). + for id: int in peers: + if id == -peer_id: + continue + if type == TYPE_STRING: + peers[id].send_text(message) + else: + peers[id].put_packet(message) + return OK + + assert(peers.has(peer_id)) + var socket: WebSocketPeer = peers[peer_id] + if type == TYPE_STRING: + return socket.send_text(message) + return socket.send(var_to_bytes(message)) + + +func get_message(peer_id: int) -> Variant: + assert(peers.has(peer_id)) + var socket: WebSocketPeer = peers[peer_id] + if socket.get_available_packet_count() < 1: + return null + var pkt: PackedByteArray = socket.get_packet() + if socket.was_string_packet(): + return pkt.get_string_from_utf8() + return bytes_to_var(pkt) + + +func has_message(peer_id: int) -> bool: + assert(peers.has(peer_id)) + return peers[peer_id].get_available_packet_count() > 0 + + +func _create_peer() -> WebSocketPeer: + var ws := WebSocketPeer.new() + ws.supported_protocols = supported_protocols + ws.handshake_headers = handshake_headers + return ws + + +func poll() -> void: + if not tcp_server.is_listening(): + return + + while not refuse_new_connections and tcp_server.is_connection_available(): + var conn: StreamPeerTCP = tcp_server.take_connection() + assert(conn != null) + pending_peers.append(PendingPeer.new(conn)) + + var to_remove := [] + + for p in pending_peers: + if not _connect_pending(p): + if p.connect_time + handshake_timout < Time.get_ticks_msec(): + # Timeout. + to_remove.append(p) + continue # Still pending. + + to_remove.append(p) + + for r: RefCounted in to_remove: + pending_peers.erase(r) + + to_remove.clear() + + for id: int in peers: + var p: WebSocketPeer = peers[id] + p.poll() + + if p.get_ready_state() != WebSocketPeer.STATE_OPEN: + client_disconnected.emit(id) + to_remove.append(id) + continue + + while p.get_available_packet_count(): + message_received.emit(id, get_message(id)) + + for r: int in to_remove: + peers.erase(r) + to_remove.clear() + + +func _connect_pending(p: PendingPeer) -> bool: + if p.ws != null: + # Poll websocket client if doing handshake. + p.ws.poll() + var state := p.ws.get_ready_state() + if state == WebSocketPeer.STATE_OPEN: + var id := randi_range(2, 1 << 30) + peers[id] = p.ws + client_connected.emit(id) + return true # Success. + elif state != WebSocketPeer.STATE_CONNECTING: + return true # Failure. + return false # Still connecting. + elif p.tcp.get_status() != StreamPeerTCP.STATUS_CONNECTED: + return true # TCP disconnected. + elif not use_tls: + # TCP is ready, create WS peer. + p.ws = _create_peer() + p.ws.accept_stream(p.tcp) + return false # WebSocketPeer connection is pending. + + else: + if p.connection == p.tcp: + assert(tls_key != null and tls_cert != null) + var tls := StreamPeerTLS.new() + tls.accept_stream(p.tcp, TLSOptions.server(tls_key, tls_cert)) + p.connection = tls + p.connection.poll() + var status: StreamPeerTLS.Status = p.connection.get_status() + if status == StreamPeerTLS.STATUS_CONNECTED: + p.ws = _create_peer() + p.ws.accept_stream(p.connection) + return false # WebSocketPeer connection is pending. + if status != StreamPeerTLS.STATUS_HANDSHAKING: + return true # Failure. + + return false + + +func _process(_delta: float) -> void: + poll() diff --git a/apworld/client/vendor/uuid.gd b/apworld/client/vendor/uuid.gd deleted file mode 100644 index b63fa04..0000000 --- a/apworld/client/vendor/uuid.gd +++ /dev/null @@ -1,195 +0,0 @@ -# Note: The code might not be as pretty it could be, since it's written -# in a way that maximizes performance. Methods are inlined and loops are avoided. -extends Node - -const BYTE_MASK: int = 0b11111111 - - -static func uuidbin(): - randomize() - # 16 random bytes with the bytes on index 6 and 8 modified - return [ - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - ((randi() & BYTE_MASK) & 0x0f) | 0x40, - randi() & BYTE_MASK, - ((randi() & BYTE_MASK) & 0x3f) | 0x80, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - randi() & BYTE_MASK, - ] - - -static func uuidbinrng(rng: RandomNumberGenerator): - rng.randomize() - return [ - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, - rng.randi() & BYTE_MASK, - ((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - rng.randi() & BYTE_MASK, - ] - - -static func v4(): - # 16 random bytes with the bytes on index 6 and 8 modified - var b = uuidbin() - - return ( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" - % [ - # low - b[0], - b[1], - b[2], - b[3], - # mid - b[4], - b[5], - # hi - b[6], - b[7], - # clock - b[8], - b[9], - # clock - b[10], - b[11], - b[12], - b[13], - b[14], - b[15] - ] - ) - - -static func v4_rng(rng: RandomNumberGenerator): - # 16 random bytes with the bytes on index 6 and 8 modified - var b = uuidbinrng(rng) - - return ( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" - % [ - # low - b[0], - b[1], - b[2], - b[3], - # mid - b[4], - b[5], - # hi - b[6], - b[7], - # clock - b[8], - b[9], - # clock - b[10], - b[11], - b[12], - b[13], - b[14], - b[15] - ] - ) - - -var _uuid: Array - - -func _init(rng := RandomNumberGenerator.new()) -> void: - _uuid = uuidbinrng(rng) - - -func as_array() -> Array: - return _uuid.duplicate() - - -func as_dict(big_endian := true) -> Dictionary: - if big_endian: - return { - "low": (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8) + _uuid[3], - "mid": (_uuid[4] << 8) + _uuid[5], - "hi": (_uuid[6] << 8) + _uuid[7], - "clock": (_uuid[8] << 8) + _uuid[9], - "node": - ( - (_uuid[10] << 40) - + (_uuid[11] << 32) - + (_uuid[12] << 24) - + (_uuid[13] << 16) - + (_uuid[14] << 8) - + _uuid[15] - ) - } - else: - return { - "low": _uuid[0] + (_uuid[1] << 8) + (_uuid[2] << 16) + (_uuid[3] << 24), - "mid": _uuid[4] + (_uuid[5] << 8), - "hi": _uuid[6] + (_uuid[7] << 8), - "clock": _uuid[8] + (_uuid[9] << 8), - "node": - ( - _uuid[10] - + (_uuid[11] << 8) - + (_uuid[12] << 16) - + (_uuid[13] << 24) - + (_uuid[14] << 32) - + (_uuid[15] << 40) - ) - } - - -func as_string() -> String: - return ( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x" - % [ - # low - _uuid[0], - _uuid[1], - _uuid[2], - _uuid[3], - # mid - _uuid[4], - _uuid[5], - # hi - _uuid[6], - _uuid[7], - # clock - _uuid[8], - _uuid[9], - # node - _uuid[10], - _uuid[11], - _uuid[12], - _uuid[13], - _uuid[14], - _uuid[15] - ] - ) - - -func is_equal(other) -> bool: - # Godot Engine compares Array recursively - # There's no need for custom comparison here. - return _uuid == other._uuid -- cgit 1.4.1