Skip to content

Commit

Permalink
Support Killing Floor Protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
BattlefieldDuck committed Jan 18, 2024
1 parent 6fea855 commit a0676df
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 24 deletions.
1 change: 1 addition & 0 deletions opengsq/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from opengsq.protocols.gamespy2 import GameSpy2
from opengsq.protocols.gamespy3 import GameSpy3
from opengsq.protocols.gamespy4 import GameSpy4
from opengsq.protocols.killingfloor import KillingFloor
from opengsq.protocols.minecraft import Minecraft
from opengsq.protocols.quake1 import Quake1
from opengsq.protocols.quake2 import Quake2
Expand Down
57 changes: 57 additions & 0 deletions opengsq/protocols/killingfloor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from opengsq.binary_reader import BinaryReader
from opengsq.exceptions import InvalidPacketException
from opengsq.protocol_socket import UdpClient
from opengsq.protocols.unreal2 import Unreal2


class KillingFloor(Unreal2):
"""Killing Floor Protocol"""
full_name = 'Killing Floor Protocol'

async def get_details(self):
response = await UdpClient.communicate(self, b'\x79\x00\x00\x00' + bytes([self._DETAILS]))

# Remove the first 4 bytes \x80\x00\x00\x00
br = BinaryReader(response[4:])
header = br.read_byte()

if header != self._DETAILS:
raise InvalidPacketException(
'Packet header mismatch. Received: {}. Expected: {}.'
.format(chr(header), chr(self._DETAILS))
)

details = {}
details['ServerId'] = br.read_long() # 0
details['ServerIP'] = br.read_string() # empty
details['GamePort'] = br.read_long()
details['QueryPort'] = br.read_long() # 0
details['ServerName'] = self._read_string(br)
details['MapName'] = self._read_string(br)
details['GameType'] = self._read_string(br)
details['NumPlayers'] = br.read_long()
details['MaxPlayers'] = br.read_long()
details['WaveCurrent'] = br.read_long()
details['WaveTotal'] = br.read_long()
details['Ping'] = br.read_long()
details['Flags'] = br.read_long()
details['Skill'] = self._read_string(br)

return details


if __name__ == '__main__':
import asyncio
import json

async def main_async():
# killingfloor
killingFloor = KillingFloor(host='104.234.65.235', port=7708, timeout=10.0)
details = await killingFloor.get_details()
print(json.dumps(details, indent=None) + '\n')
rules = await killingFloor.get_rules()
print(json.dumps(rules, indent=None) + '\n')
players = await killingFloor.get_players()
print(json.dumps(players, indent=None) + '\n')

asyncio.run(main_async())
35 changes: 11 additions & 24 deletions opengsq/protocols/unreal2.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,14 @@ async def get_details(self):
details['ServerIP'] = br.read_string() # empty
details['GamePort'] = br.read_long()
details['QueryPort'] = br.read_long() # 0
details['ServerName'] = self.__read_string(br)
details['MapName'] = self.__read_string(br)
details['GameType'] = self.__read_string(br)
details['ServerName'] = self._read_string(br)
details['MapName'] = self._read_string(br)
details['GameType'] = self._read_string(br)
details['NumPlayers'] = br.read_long()
details['MaxPlayers'] = br.read_long()

if br.remaining_bytes() > 12:
try:
# Killing Floor
stream_position = br.stream_position
details['WaveCurrent'] = br.read_long()
details['WaveTotal'] = br.read_long()
details['Ping'] = br.read_long()
details['Flags'] = br.read_long()
details['Skill'] = self.__read_string(br)
except Exception:
br.stream_position = stream_position
details['Ping'] = br.read_long()
details['Flags'] = br.read_long()
details['Skill'] = self.__read_string(br)
details['Ping'] = br.read_long()
details['Flags'] = br.read_long()
details['Skill'] = self._read_string(br)

return details

Expand All @@ -72,8 +60,8 @@ async def get_rules(self):
rules['Mutators'] = []

while not br.is_end():
key = self.__read_string(br)
val = self.__read_string(br)
key = self._read_string(br)
val = self._read_string(br)

if key.lower() == 'mutator':
rules['Mutators'].append(val)
Expand All @@ -100,7 +88,7 @@ async def get_players(self):
while not br.is_end():
player = {}
player['Id'] = br.read_long()
player['Name'] = self.__read_string(br)
player['Name'] = self._read_string(br)
player['Ping'] = br.read_long()
player['Score'] = br.read_long()
player['StatsId'] = br.read_long()
Expand All @@ -111,10 +99,9 @@ async def get_players(self):
@staticmethod
def strip_colors(text: bytes):
"""Strip color codes"""
string = re.compile(b'\x7f|[\x00-\x1a]|[\x1c-\x1f]').sub(b'', text)
return string.replace(b'\x1b@@', b'').replace(b'\x1b@', b'').replace(b'\x1b', b'')
return re.compile(b'\x1b...|[\x00-\x1a]').sub(b'', text)

def __read_string(self, br: BinaryReader):
def _read_string(self, br: BinaryReader):
length = br.read_byte()
string = br.read_string()

Expand Down
27 changes: 27 additions & 0 deletions tests/protocols/test_killingfloor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os

import pytest
from opengsq.protocols.killingfloor import KillingFloor

from .result_handler import ResultHandler

handler = ResultHandler(os.path.basename(__file__)[:-3])
# handler.enable_save = True

# Killing Floor
test = KillingFloor(host='104.234.65.235', port=7708)

@pytest.mark.asyncio
async def test_get_details():
result = await test.get_details()
await handler.save_result('test_get_details', result)

@pytest.mark.asyncio
async def test_get_rules():
result = await test.get_rules()
await handler.save_result('test_get_rules', result)

@pytest.mark.asyncio
async def test_get_players():
result = await test.get_players()
await handler.save_result('test_get_players', result)
16 changes: 16 additions & 0 deletions tests/results/test_killingfloor/test_get_details.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"ServerId": 0,
"ServerIP": "",
"GamePort": 7707,
"QueryPort": 0,
"ServerName": "|uDeep|/T\\||Noi| #2 <<uINFERNA> W",
"MapName": "KF-STALKER-DUTY-BASE(REMAKE)",
"GameType": "KFGameType",
"NumPlayers": 17,
"MaxPlayers": 32,
"WaveCurrent": 2,
"WaveTotal": 7,
"Ping": 0,
"Flags": 512,
"Skill": "0"
}
100 changes: 100 additions & 0 deletions tests/results/test_killingfloor/test_get_players.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
[
{
"Id": 769,
"Name": "[CHL] asein",
"Ping": 56,
"Score": 1500,
"StatsId": 536870912
},
{
"Id": 554,
"Name": "[MEX] Tologuin",
"Ping": 148,
"Score": 1515,
"StatsId": 536870912
},
{
"Id": 374,
"Name": "[CHL] fafe_asta",
"Ping": 60,
"Score": 984,
"StatsId": 536870912
},
{
"Id": 311,
"Name": "[ARG] Rekiam",
"Ping": 76,
"Score": 1129,
"StatsId": 536870912
},
{
"Id": 277,
"Name": "[ARG] KFL/UFOKILLER",
"Ping": 28,
"Score": 1716,
"StatsId": 536870912
},
{
"Id": 271,
"Name": "[KOR] Bohemian",
"Ping": 252,
"Score": 493,
"StatsId": 536870912
},
{
"Id": 199,
"Name": "[BRA] Finish",
"Ping": 40,
"Score": 0,
"StatsId": 0
},
{
"Id": 83,
"Name": "[CHL] LordChalox",
"Ping": 60,
"Score": 2282,
"StatsId": 536870912
},
{
"Id": 29,
"Name": "[CHL] Kriss_Nemesis",
"Ping": 52,
"Score": 226,
"StatsId": 536870912
},
{
"Id": 21,
"Name": "[ESP] HomerO",
"Ping": 176,
"Score": 175,
"StatsId": 536870912
},
{
"Id": 19,
"Name": "[CHL] Marcelo_Kris",
"Ping": 56,
"Score": 229,
"StatsId": 536870912
},
{
"Id": 18,
"Name": "[CHL] ZuraRxx",
"Ping": 92,
"Score": 2046,
"StatsId": 536870912
},
{
"Id": 17,
"Name": "[ARG] francomaldonado",
"Ping": 48,
"Score": 59,
"StatsId": 536870912
},
{
"Id": 15,
"Name": "[SLV] memtrix",
"Ping": 128,
"Score": 569,
"StatsId": 536870912
}
]
27 changes: 27 additions & 0 deletions tests/results/test_killingfloor/test_get_rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"Mutators": [
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase",
"ZNBase"
],
"ServerMode": "dedicated",
"AdminName": "AdminEmail",
"ServerVersion": "1065",
"IsVacSecured": "true",
"MaxSpectators": "6",
"MapVoting": "true",
"KickVoting": "true",
"SP: Version": "750",
"SP: Min perk level": "6",
"SP: Max perk level": "99",
"SP: Num trader weapons": "140",
"SP: Perk 1": "Support Specialist",
"SP: Perk 2": "Berserker"
}

0 comments on commit a0676df

Please sign in to comment.