Skip to content

Commit e101b57

Browse files
Support Satisfactory Protocol
1 parent e8b3f2a commit e101b57

File tree

7 files changed

+90
-4
lines changed

7 files changed

+90
-4
lines changed

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ from opengsq.protocols.quake2 import Quake2
2323
from opengsq.protocols.quake3 import Quake3
2424
from opengsq.protocols.raknet import Raknet
2525
from opengsq.protocols.samp import Samp
26+
from opengsq.protocols.satisfactory import Satisfactory
2627
from opengsq.protocols.source import Source
2728
from opengsq.protocols.teamspeak3 import Teamspeak3
2829
from opengsq.protocols.unreal2 import Unreal2
@@ -66,6 +67,8 @@ asyncio.run(main())
6667
Rcon server using Source Remote Console, example output: [tests/results/test_source/test_remote_console.txt](/tests/results/test_source/test_remote_console.txt)
6768
```py
6869
import asyncio
70+
71+
from opengsq.exceptions import AuthenticationException
6972
from opengsq.protocols import Source
7073

7174
async def main():
@@ -74,9 +77,9 @@ async def main():
7477
await rcon.authenticate('serverRconPassword')
7578
result = await rcon.send_command('cvarlist')
7679
print(result)
77-
except:
80+
except AuthenticationException:
7881
print('Fail to authenticate')
79-
82+
8083
asyncio.run(main())
8184
```
8285

opengsq/cli.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
from typing import Mapping, Sequence
1111

1212
from opengsq.protocol_base import ProtocolBase
13+
from opengsq.version import __version__
1314

1415

1516
class CLI:
1617
def __init__(self):
1718
self.__paths = {}
1819

1920
def register(self, parser: argparse.ArgumentParser):
21+
# Add version argument
22+
parser.add_argument('-V', '--version', action='store_true', help='print the opengsq version number and exit')
23+
2024
opengsq_path = os.path.abspath(os.path.dirname(__file__))
2125
subparsers = parser.add_subparsers(dest='subparser_name')
2226
pattern = re.compile(r'from\s+(\S+)\s+import\s+(.+,?\S)')
@@ -32,7 +36,7 @@ def register(self, parser: argparse.ArgumentParser):
3236
self.__paths[name] = fullpath
3337

3438
# Add parser and arguments
35-
obj = locate(fullpath)
39+
obj: ProtocolBase = locate(fullpath)
3640
sub = subparsers.add_parser(name, help=obj.full_name)
3741
self.__add_arguments(sub, parameters)
3842
method_names = [func for func in dir(obj) if callable(getattr(obj, func)) and func.startswith('get_')]
@@ -41,6 +45,12 @@ def register(self, parser: argparse.ArgumentParser):
4145

4246
# Get the query response in json format
4347
async def run(self, args: Sequence[str]) -> str:
48+
# Return version if -V or --version
49+
if args.version:
50+
return __version__
51+
else:
52+
del args.version
53+
4454
# Load the obj from path
4555
obj = locate(self.__paths[args.subparser_name])
4656
del args.subparser_name

opengsq/protocols/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from opengsq.protocols.quake3 import Quake3
1212
from opengsq.protocols.raknet import Raknet
1313
from opengsq.protocols.samp import Samp
14+
from opengsq.protocols.satisfactory import Satisfactory
1415
from opengsq.protocols.source import Source
1516
from opengsq.protocols.teamspeak3 import Teamspeak3
1617
from opengsq.protocols.unreal2 import Unreal2

opengsq/protocols/satisfactory.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import struct
2+
3+
from opengsq.binary_reader import BinaryReader
4+
from opengsq.exceptions import InvalidPacketException
5+
from opengsq.protocol_base import ProtocolBase
6+
from opengsq.socket_async import SocketAsync
7+
8+
9+
class Satisfactory(ProtocolBase):
10+
"""Satisfactory Protocol"""
11+
full_name = 'Satisfactory Protocol'
12+
13+
async def get_status(self) -> dict:
14+
"""
15+
Retrieves information about the server including state, version, and beacon port
16+
Server state: 1 - Idle (no game loaded), 2 - currently loading or creating a game, 3 - currently in game
17+
"""
18+
# Credit: https://github.com/dopeghoti/SF-Tools/blob/main/Protocol.md
19+
20+
# Send message id, protocol version
21+
request = struct.pack('2b', 0, 0) + 'opengsq'.encode()
22+
response = await SocketAsync.send_and_receive(self._address, self._query_port, self._timeout, request)
23+
br = BinaryReader(response)
24+
header = br.read_byte()
25+
26+
if header != 1:
27+
raise InvalidPacketException('Packet header mismatch. Received: {}. Expected: {}.'.format(chr(header), chr(1)))
28+
29+
br.read_byte() # Protocol version
30+
br.read_bytes(8) # Request data
31+
32+
result = {}
33+
result['State'] = br.read_byte()
34+
result['Version'] = br.read_long()
35+
result['BeaconPort'] = br.read_short()
36+
37+
return result
38+
39+
40+
if __name__ == '__main__':
41+
import asyncio
42+
import json
43+
44+
async def main_async():
45+
satisfactory = Satisfactory(address='delta3.ptse.host', query_port=15777, timeout=5.0)
46+
status = await satisfactory.get_status()
47+
print(json.dumps(status, indent=None) + '\n')
48+
49+
asyncio.run(main_async())

opengsq/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.4.5'
1+
__version__ = '1.5.0'

tests/protocols/test_satisfactory.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import os
2+
3+
import pytest
4+
5+
from opengsq.protocols.satisfactory import Satisfactory
6+
7+
from .result_handler import ResultHandler
8+
9+
handler = ResultHandler(os.path.basename(__file__)[:-3])
10+
handler.enable_save = True
11+
12+
# Satisfactory
13+
test = Satisfactory(address='delta3.ptse.host', query_port=15777)
14+
15+
@pytest.mark.asyncio
16+
async def test_get_status():
17+
result = await test.get_status()
18+
await handler.save_result('test_get_status', result)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"State": 3,
3+
"Version": 211839,
4+
"BeaconPort": 15000
5+
}

0 commit comments

Comments
 (0)