Skip to content

Commit cc27fa9

Browse files
Merge pull request #32 from Hornochs/add-udk-ut3
Add LAN UDK Query Support and UT3 Servers on LAN Support
2 parents 28f7a91 + 12481a1 commit cc27fa9

File tree

14 files changed

+625
-25
lines changed

14 files changed

+625
-25
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ from opengsq.protocols import (
3838
Scum,
3939
Source,
4040
TeamSpeak3,
41+
UDK,
4142
Unreal2,
43+
UT3,
4244
Vcmp,
4345
WON,
4446
)

docs/tests/protocols/index.rst

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,30 @@ Protocols Tests
44
===============
55

66
.. toctree::
7-
test_ase/index
8-
test_battlefield/index
9-
test_doom3/index
10-
test_eos/index
11-
test_fivem/index
12-
test_gamespy1/index
13-
test_gamespy2/index
14-
test_gamespy3/index
157
test_gamespy4/index
16-
test_kaillera/index
17-
test_killingfloor/index
8+
test_teamspeak3/index
9+
test_won/index
10+
test_gamespy1/index
1811
test_minecraft/index
19-
test_nadeo/index
20-
test_palworld/index
21-
test_quake1/index
22-
test_quake2/index
23-
test_quake3/index
2412
test_raknet/index
13+
test_eos/index
14+
test_kaillera/index
15+
test_ase/index
16+
test_quake1/index
17+
test_killingfloor/index
18+
test_source/index
2519
test_samp/index
26-
test_satisfactory/index
2720
test_scum/index
28-
test_source/index
29-
test_teamspeak3/index
21+
test_ut3/index
3022
test_unreal2/index
23+
test_quake3/index
24+
test_nadeo/index
25+
test_battlefield/index
26+
test_fivem/index
27+
test_palworld/index
28+
test_quake2/index
29+
test_gamespy2/index
30+
test_doom3/index
3131
test_vcmp/index
32-
test_won/index
32+
test_satisfactory/index
33+
test_gamespy3/index
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.. _test_ut3:
2+
3+
test_ut3
4+
========
5+
6+
.. toctree::
7+
test_ut3_status
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
test_ut3_status
2+
===============
3+
4+
Here are the results for the test method.
5+
6+
.. code-block:: json
7+
8+
{
9+
"name": "Test UT3 InstaGib Server",
10+
"map": "DM-Deck",
11+
"game_type": "UTGame.UTDeathmatch",
12+
"num_players": 0,
13+
"max_players": 32,
14+
"password_protected": false,
15+
"stats_enabled": false,
16+
"lan_mode": true,
17+
"players": [],
18+
"raw": {
19+
"hostaddress": "0.0.0.0",
20+
"hostport": 7777,
21+
"num_players": 0,
22+
"max_players": 32,
23+
"lan_mode": true,
24+
"uses_stats": false,
25+
"owner_id": "0000000000000000",
26+
"owner_name": "Test UT3 InstaGib Server",
27+
"localized_settings": [
28+
{
29+
"id": 32779,
30+
"value_index": 0,
31+
"advertisement_type": 1
32+
},
33+
{
34+
"id": 0,
35+
"value_index": 2,
36+
"advertisement_type": 1
37+
},
38+
{
39+
"id": 1,
40+
"value_index": 0,
41+
"advertisement_type": 1
42+
},
43+
{
44+
"id": 6,
45+
"value_index": 1,
46+
"advertisement_type": 1
47+
},
48+
{
49+
"id": 7,
50+
"value_index": 0,
51+
"advertisement_type": 1
52+
},
53+
{
54+
"id": 8,
55+
"value_index": 0,
56+
"advertisement_type": 1
57+
},
58+
{
59+
"id": 9,
60+
"value_index": 0,
61+
"advertisement_type": 1
62+
},
63+
{
64+
"id": 10,
65+
"value_index": 0,
66+
"advertisement_type": 1
67+
},
68+
{
69+
"id": 11,
70+
"value_index": 0,
71+
"advertisement_type": 1
72+
},
73+
{
74+
"id": 12,
75+
"value_index": 0,
76+
"advertisement_type": 1
77+
},
78+
{
79+
"id": 13,
80+
"value_index": 1,
81+
"advertisement_type": 1
82+
},
83+
{
84+
"id": 14,
85+
"value_index": 1,
86+
"advertisement_type": 1
87+
}
88+
],
89+
"settings_properties": [
90+
{
91+
"id": 1073741825,
92+
"data": "DM-Deck",
93+
"advertisement_type": 2
94+
},
95+
{
96+
"id": 1073741826,
97+
"data": "UTGame.UTDeathmatch",
98+
"advertisement_type": 2
99+
},
100+
{
101+
"id": 268435704,
102+
"data": 25,
103+
"advertisement_type": 1
104+
},
105+
{
106+
"id": 268435705,
107+
"data": 20,
108+
"advertisement_type": 1
109+
},
110+
{
111+
"id": 268435703,
112+
"data": 6,
113+
"advertisement_type": 1
114+
},
115+
{
116+
"id": 1073741827,
117+
"data": "",
118+
"advertisement_type": 2
119+
},
120+
{
121+
"id": 268435717,
122+
"data": 32,
123+
"advertisement_type": 1
124+
},
125+
{
126+
"id": 1073741828,
127+
"data": "",
128+
"advertisement_type": 2
129+
},
130+
{
131+
"id": 1073741829,
132+
"data": "",
133+
"advertisement_type": 2
134+
},
135+
{
136+
"id": 268435706,
137+
"data": 6,
138+
"advertisement_type": 0
139+
},
140+
{
141+
"id": 268435968,
142+
"data": 0,
143+
"advertisement_type": 0
144+
},
145+
{
146+
"id": 268435969,
147+
"data": 0,
148+
"advertisement_type": 0
149+
}
150+
],
151+
"map": "DM-Deck",
152+
"gametype": "UTGame.UTDeathmatch",
153+
"frag_limit": 25,
154+
"time_limit": 20,
155+
"numbots": 6,
156+
"stock_mutators": [
157+
"Instagib"
158+
],
159+
"custom_mutators": [],
160+
"gamemode": "Deathmatch",
161+
"bot_skill": "Experienced",
162+
"pure_server": 1,
163+
"password": 0,
164+
"vs_bots": "None",
165+
"force_respawn": 0
166+
}
167+
}

opengsq/protocol_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ def __init__(self, host: str, port: int, timeout: float = 5.0):
1111
self._host = host
1212
self._port = port
1313
self._timeout = timeout
14+
self._allow_broadcast = False

opengsq/protocol_socket.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,41 @@ class Socket():
1515
async def gethostbyname(hostname: str):
1616
return await asyncio.get_running_loop().run_in_executor(None, socket.gethostbyname, hostname)
1717

18+
class Protocol(asyncio.Protocol):
19+
def __init__(self, timeout: float):
20+
self.__packets = asyncio.Queue()
21+
self.__timeout = timeout
22+
23+
async def recv(self):
24+
return await asyncio.wait_for(self.__packets.get(), timeout=self.__timeout)
25+
26+
def connection_made(self, transport):
27+
pass
28+
29+
def connection_lost(self, exc):
30+
pass
31+
32+
def data_received(self, data):
33+
self.__packets.put_nowait(data)
34+
35+
def eof_received(self):
36+
pass
37+
38+
def datagram_received(self, data, addr):
39+
self.__packets.put_nowait(data)
40+
41+
def error_received(self, exc):
42+
pass
43+
1844
def __init__(self, kind: SocketKind):
1945
self.__timeout = None
2046
self.__transport = None
2147
self.__protocol = None
2248
self.__kind = kind
49+
self.__local_port = None
50+
51+
def bind_port(self, port: int):
52+
self.__local_port = port
2353

2454
async def __aenter__(self):
2555
return self
@@ -48,11 +78,13 @@ async def __connect(self, remote_addr):
4878
lambda: self.__protocol,
4979
host=remote_addr[0],
5080
port=remote_addr[1],
81+
local_addr=('0.0.0.0', self.__local_port) if self.__local_port else None
5182
)
5283
else:
5384
self.__transport, _ = await loop.create_datagram_endpoint(
5485
lambda: self.__protocol,
5586
remote_addr=remote_addr,
87+
local_addr=('0.0.0.0', self.__local_port) if self.__local_port else None
5688
)
5789

5890
def close(self):
@@ -115,12 +147,24 @@ def error_received(self, exc):
115147

116148
class UdpClient(Socket):
117149
@staticmethod
118-
async def communicate(protocol: ProtocolBase, data: bytes):
150+
async def communicate(protocol: ProtocolBase, data: bytes, source_port: int = None):
119151
with UdpClient() as udpClient:
152+
if source_port:
153+
udpClient.bind_port(source_port)
120154
udpClient.settimeout(protocol._timeout)
121-
await udpClient.connect((protocol._host, protocol._port))
122-
udpClient.send(data)
123-
return await udpClient.recv()
155+
156+
loop = asyncio.get_running_loop()
157+
transport, protocol_instance = await loop.create_datagram_endpoint(
158+
lambda: Socket.Protocol(protocol._timeout), # Use public Protocol class
159+
local_addr=('0.0.0.0', source_port if source_port else 0),
160+
allow_broadcast=protocol._allow_broadcast
161+
)
162+
163+
try:
164+
transport.sendto(data, (protocol._host, protocol._port))
165+
return await protocol_instance.recv()
166+
finally:
167+
transport.close()
124168

125169
def __init__(self):
126170
super().__init__(SocketKind.SOCK_DGRAM)
@@ -150,4 +194,4 @@ async def test_socket_async():
150194

151195
loop = asyncio.get_event_loop()
152196
loop.run_until_complete(test_socket_async())
153-
loop.close()
197+
loop.close()

opengsq/protocols/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from opengsq.protocols.scum import Scum
2222
from opengsq.protocols.source import Source
2323
from opengsq.protocols.teamspeak3 import TeamSpeak3
24+
from opengsq.protocols.udk import UDK
2425
from opengsq.protocols.unreal2 import Unreal2
26+
from opengsq.protocols.ut3 import UT3
2527
from opengsq.protocols.vcmp import Vcmp
26-
from opengsq.protocols.won import WON
28+
from opengsq.protocols.won import WON

0 commit comments

Comments
 (0)