Compare commits

..

20 Commits

Author SHA1 Message Date
c258c520a5 jk, just continue to build ontop (added better debug prints, colors, bitstream additions, continuation for bitstream parsing, and currently working on acks) 2021-05-10 22:45:41 +02:00
4812d9c35d Commit before bitstream rewrite 2021-05-04 15:50:53 +02:00
6d9c0805ac except * -> Exception (doesnt matter in this context but should do it) 2021-05-04 13:34:05 +02:00
4acb1adeb4 Travel <o/ 2021-04-25 17:39:47 +02:00
59b30ed591 Moved classes from init files 2021-03-25 00:00:39 +01:00
f9d8470ee6 Type hint correctly 2021-03-04 14:31:18 +01:00
483587fd3b BitStreams 2021-03-04 13:40:32 +01:00
1b78fb98bc Commit before potential bitstream rewrite 2020-04-05 15:31:55 +02:00
0cd474601a Packet handling order of operations notes 2020-04-04 23:12:24 +02:00
37e732e19f Added notes to log 2020-04-04 18:31:11 +02:00
deb4bb7483 New log file with logging of ReadBit 2020-04-04 16:17:02 +02:00
3d359a4424 Server dump (wrong password attempt) 2020-04-04 16:06:03 +02:00
558be67f61 Research notes 2020-04-04 16:04:40 +02:00
cb2d31e033 Implemented compressor 2020-03-30 01:41:07 +02:00
a30b9f1a62 Rewrote timeout system 2020-03-30 00:59:53 +02:00
e9ef282c95 Keep alive system 2020-03-30 00:38:02 +02:00
f55275a627 Preparement for rcon support 2020-03-29 23:43:08 +02:00
c1ad95d233 Query protocol 2020-03-29 23:05:00 +02:00
0c0a8d327c Rename imports 2020-03-29 19:12:31 +02:00
ee08f6a3b1 Pass server config to clients 2020-03-29 19:04:20 +02:00
30 changed files with 1503 additions and 121 deletions

4
.gitignore vendored
View File

@@ -52,7 +52,6 @@ coverage.xml
*.pot *.pot
# Django stuff: # Django stuff:
*.log
local_settings.py local_settings.py
# Flask stuff: # Flask stuff:
@@ -96,3 +95,6 @@ ENV/
# Logs # Logs
logs/ logs/
# vscode
.vscode/

View File

@@ -3,7 +3,6 @@
"logging": { "logging": {
"filename": "", "filename": "",
"level": "DEBUG", "level": "DEBUG",
"format": "[%(name)s:L%(lineno)02d %(levelname)s] %(message)s",
"datefmt": "%d-%b-%y %H:%M:%S" "datefmt": "%d-%b-%y %H:%M:%S"
} }
}, },
@@ -14,6 +13,7 @@
"port": 7777, "port": 7777,
"hostname": "Python > C", "hostname": "Python > C",
"password": "", "password": "",
"rcon_password": "changeme",
"max_players": 50, "max_players": 50,
"mode": "debug", "mode": "debug",
"language": "python" "language": "python"

View File

@@ -1,12 +1,12 @@
from sampy.env import Environment from sampy.env import Environment
from sampy.struct.server import Server from sampy.struct.server import ServerConfig
from sampy.shared.glob import config from sampy.shared.glob import config
environments = [] environments = []
for server in config["demo"]["servers"]: for server in config["demo"]["servers"]:
server_config = Server(**server) # Initialize a new Server struct every time even if you are just changing the port (required due to reference and automation values) server_config = ServerConfig(**server) # Initialize a new Server struct every time even if you are just changing the port (required due to reference and automation values)
env = Environment(server_config) env = Environment(server_config)
environments.append(env) environments.append(env)

123
notes/dumps/1586009002.log Normal file
View File

@@ -0,0 +1,123 @@
----------
Loaded log file: "server_log.txt".
----------
SA-MP Dedicated Server
----------------------
v0.3.7-R2, (C)2005-2015 SA-MP Team
Server Plugins
--------------
Loading plugin: SAMPExtender
Loaded.
Loaded 1 plugins.
SAMP Extender
-------------
Callback hooks:
[x] 0x0045F7E0 -> 0x7A4F119A: CodeTrace::Run(0x0045f7e0)
[x] 0x0045C000 -> 0x7A4F1118: CodeTrace::Run(0x0045c000)
[x] 0x0044DA70 -> 0x7A4F1276: CodeTrace::Run(0x0044da70)
[x] 0x0044DB30 -> 0x7A4F140B: CodeTrace::Run(0x0044db30)
[x] 0x0044DD00 -> 0x7A4F1410: CodeTrace::Run(0x0044dd00)
[x] 0x0044DED0 -> 0x7A4F1415: CodeTrace::Run(0x0044ded0)
Installed: 6
Failed: 0
Ban list
--------
Loaded: samp.ban
Started server on port: 7777, with maxplayers: 50 lanmode is OFF.
Filterscripts
---------------
Loaded 0 filterscripts.
Number of vehicle models: 1
[connection] 127.0.0.1:64555 requests connection cookie.
[connection] incoming connection: 127.0.0.1:64555 id: 0
HandleSocketReceiveFromConnectedPlayer()
caller_addr: 0x00456C9C
length: 22
MTUSize: 576
Player port: 64555
buffer:
00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 64 64 64 64 00
CreateInternalPacketFromBitStream()
caller_addr: 0x0045FB0D
time: 21 -905708544
bitStream->numberOfBitsAllocated: 176
bitStream->numberOfBitsUsed: 176
bitStream->readOffset: 1
bitStream->data:
00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 64 64 64 64 00
bitStream->data(bits):
0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadBits()
caller_addr: 0x0045C046
alignBitsToRight: 1
numberOfBitsToRead: 16
bitstream->read(bits):
0[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadBits()
caller_addr: 0x0045C063
alignBitsToRight: 1
numberOfBitsToRead: 4
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[1 0 0 0]0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadCompressed()
caller_addr: 0x0045C0FF
size: 16
unsignedData: 1
BitStream::ReadBits()
caller_addr: 0x0044DC21
alignBitsToRight: 1
numberOfBitsToRead: 8
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 [1 0 0 1 0 0 0 0] 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadAlignedBytes()
caller_addr: 0x0045C14A
numberOfBytesToRead: 18
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 [0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
CreateInternalPacketFromBitStream()
caller_addr: 0x00460130
time: 21 -905708544
bitStream->numberOfBitsAllocated: 176
bitStream->numberOfBitsUsed: 176
bitStream->readOffset: 176
bitStream->data:
00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 64 64 64 64 00
bitStream->data(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0

148
notes/dumps/1586009798.log Normal file
View File

@@ -0,0 +1,148 @@
----------
Loaded log file: "server_log.txt".
----------
SA-MP Dedicated Server
----------------------
v0.3.7-R2, (C)2005-2015 SA-MP Team
Server Plugins
--------------
Loading plugin: SAMPExtender
Loaded.
Loaded 1 plugins.
SAMP Extender
-------------
Callback hooks:
[x] 0x0045F7E0 -> 0x7A53119A: CodeTrace::Run(0x0045f7e0)
[x] 0x0045C000 -> 0x7A531118: CodeTrace::Run(0x0045c000)
[x] 0x0044DA70 -> 0x7A531276: CodeTrace::Run(0x0044da70)
[x] 0x0044DB30 -> 0x7A53140B: CodeTrace::Run(0x0044db30)
[x] 0x0044DD00 -> 0x7A531410: CodeTrace::Run(0x0044dd00)
[x] 0x0044DED0 -> 0x7A531415: CodeTrace::Run(0x0044ded0)
[x] 0x0044D840 -> 0x7A53141A: CodeTrace::Run(0x0044d840)
Installed: 7
Failed: 0
Ban list
--------
Loaded: samp.ban
Started server on port: 7777, with maxplayers: 50 lanmode is OFF.
Filterscripts
---------------
Loaded 0 filterscripts.
Number of vehicle models: 1
[connection] 127.0.0.1:56092 requests connection cookie.
[connection] incoming connection: 127.0.0.1:56092 id: 0
HandleSocketReceiveFromConnectedPlayer()
caller_addr: 0x00456C9C
length: 22
MTUSize: 576
Player port: 56092
buffer:
00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 64 64 64 64 00
BitStream::ReadBit() # hasAcks
caller_addr: 0x0045F93C
bitstream->read(bits):
[0]0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
CreateInternalPacketFromBitStream()
caller_addr: 0x0045FB0D
time: 22 39423557
bitStream->numberOfBitsAllocated: 176 # 22 bytes
bitStream->numberOfBitsUsed: 176
bitStream->readOffset: 1
bitStream->data:
00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 64 64 64 64 00
bitStream->data(bits):
0|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadBits() # messageNumber
caller_addr: 0x0045C046
alignBitsToRight: 1
numberOfBitsToRead: 16
bitstream->read(bits):
0[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadBits() # reliability
caller_addr: 0x0045C063
alignBitsToRight: 1
numberOfBitsToRead: 4
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0[1 0 0 0]0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadBit() # isSplitPacket
caller_addr: 0x0045C0BC
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0[0]1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadCompressed() # unsure how this works
caller_addr: 0x0045C0FF
size: 16
unsignedData: 1
BitStream::ReadBit() # inside ReadCompressed
caller_addr: 0x0044DBC6
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1[0] 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadBits() # inside ReadCompressed
caller_addr: 0x0044DC21
alignBitsToRight: 1
numberOfBitsToRead: 8
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 [1 0 0 1 0 0 0 0] 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
BitStream::ReadAlignedBytes() # the actual data (back inside CreateInternalPacketFromBitStream now)
caller_addr: 0x0045C14A
numberOfBytesToRead: 18
bitstream->read(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 [0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0
CreateInternalPacketFromBitStream()
caller_addr: 0x00460130
time: 22 39423557
bitStream->numberOfBitsAllocated: 176
bitStream->numberOfBitsUsed: 176
bitStream->readOffset: 176
bitStream->data:
00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 64 64 64 64 00
bitStream->data(bits):
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 1 1 0 1 1 0 0 0 0 1
0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 0
0 1 1 0 0 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0
0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0

45
notes/notes.txt Normal file
View File

@@ -0,0 +1,45 @@
SendImmediate( (char*)bitStream.GetData(), bitStream.GetNumberOfBitsUsed(), SYSTEM_PRIORITY, UNRELIABLE, 0, target, false, false, currentTimeNS )
char* data = bitStream.GetData()
int numberOfBitsToSend = bitStream.GetNumberOfBitsUsed()
PacketPriority priority = SYSTEM_PRIORITY
PacketReliability reliability = UNRELIABLE
char orderingChannel = 0
PlayerID playerId = target
bool broadcast = false
bool useCallerDataAllocation = false
RakNetTimeNS currentTime = currentTimeNS
Send( char *data, int numberOfBitsToSend, PacketPriority priority, PacketReliability reliability, unsigned char orderingChannel, bool makeDataCopy, int MTUSize, RakNetTimeNS currentTime )
char* data = bitStream.GetData()
int numberOfBitsToSend = bitStream.GetNumberOfBitsUsed()
PacketPriority priority = SYSTEM_PRIORITY
PacketReliability reliability = UNRELIABLE
char orderingChannel = 0
bool makeDataCopy = true
int MTUSize = 576
RakNetTimeNS currentTime = currentTimeNS
internalPacket->creationTime = currentTime
internalPacket->data = data // (cpy)
internalPacket->dataBitLength = numberOfBitsToSend
internalPacket->nextActionTime = 0
internalPacket->messageNumber = messageNumber
internalPacket->priority = priority
internalPacket->reliability = reliability
internalPacket->splitPacketCount = 0
int headerLength = BITS_TO_BYTES( GetBitStreamHeaderLength(internalPacket) )
int maxDataSize = 576 - 28 - headerLength // MTUSize - UDP_HEADER_SIZE - headerLength
; More stuff to note from
...
sendPacketSet[ internalPacket->priority ].Push( internalPacket );

44
notes/packet_writeup.md Normal file
View File

@@ -0,0 +1,44 @@
# General packet
## Client to server spesific
* Encode (encrypt) data
* use first byte as checksum
* every byte ^ 0xAA added together
* xor
* Server port ^ 0xCCCC
* Every other byte
* Lookup table (use byte as indexing value)
## Shared
* Can be internal packet
* If packet is smaller then 3 bytes; forward to plugin handlers (*)before handling them (len(data) <= 2)
## Order of operations
1. Check if banned (if true, cancel rest and handle) [^1]
2. If first byte in data is (0x19, 0x1d, 0x1f, 0x24 or 0x18) custom code flow required [^2]
3. HandleSocketReceiveFromConnectedPlayer (cancel rest if handled)
4. Check if cheat (modified) packet (unexpected at this time, there are some packets we can expect) [^3]
[^1]: this could be changed in custom implementation (dont allow inital connection instead)
[^2]: if (0x18 and length == 2) or (0x19 and length <= 2):
handle packet and return
elif (0x1d or 0x1f or 0x24) and length <= 2:
handle packet, but continue
[^3]: if ((0x18 or 0x1a) and length <= 3) or ((0x19 or 0x1d) and length <= 2) or
((0x08 or 0x07 or 0x27) and length >= 5) or (0x37 and length < 400):
We expect this packet, everything is ok?
else:
This is a cheat (modified / injected) packet that should not arrive at this time
Send a packet to client that you received a modified packet (0x26)
# Internal packet
|condition|name|bit length|type|notes|
|---|---|---|---|---|
||messageNumber|0x10|ushort||
||reliability|0x04|uchar|Has to be over 5 (value > 5)|
|reliability in (7, 10, 9)|orderingChannel|0x05|||
|reliability in (7, 10, 9)|orderingIndex|0x10|ushort||
||isSplitPacket|0x01|bool|Drop packet as we no longer support split packet?|
|isSplitPacket == 0|dataBitLength|0x10|ushort||
|isSplitPacket == 0|data|dataBitLength|uchar*||

12
notes/packetlist.txt Normal file
View File

@@ -0,0 +1,12 @@
0x07 ID_PING
0x08 ID_PING_OPEN_CONNECTIONS
0x0B ID_CONNECTION_REQUEST
0x18 ID_OPEN_CONNECTION_REQUEST
0x19 ID_OPEN_CONNECTION_REPLY
0x1D ID_CONNECTION_ATTEMPT_FAILED
0x1F ID_NO_FREE_INCOMING_CONNECTIONS
0x24 ID_CONNECTION_BANNED
0x25 ID_INVALID_PASSWORD
0x26 ID_MODIFIED_PACKET
0x27 ID_PONG
0x37 ID_ADVERTISE_SYSTEM

View File

@@ -0,0 +1,3 @@
0x07 UNRELIABLE_SEQUENCED
0x0A RELIABLE_SEQUENCED
0x09 RELIABLE_ORDERED

View File

@@ -1,7 +1,10 @@
from . import env from . import helpers
from . import server from . import server
from . import client from . import client
from . import shared from . import shared
from . import struct from . import struct
from . import client
from . import env
#from . import logging

View File

@@ -1,32 +1,6 @@
import struct
import socket
from . import base from . import base
from . import query from . import query
from . import player from . import player
from . import client
STATE_UNKNOWN = (0, base.BaseClient) from .client import Client
STATE_QUERY = (1, query.QueryClient)
STATE_PLAYER = (2, player.PlayerClient)
class Client:
def __init__(self, socket: socket.socket, ip: str, port: int):
self.socket = socket
self.ip = ip
self.port = port
self.set_state(STATE_UNKNOWN)
def set_state(self, state: tuple):
self.state = state
self.client = self.state[1](self.socket, self.ip, self.port)
async def on_packet(self, packet: bytes):
if self.state == STATE_UNKNOWN:
# We are currently unaware if this is a player client or query client, but we got a packet that will be our check to know
if packet.startswith(b"SAMP"):
self.set_state(STATE_QUERY)
else:
self.set_state(STATE_PLAYER)
await self.client.on_packet(packet)

View File

@@ -4,9 +4,10 @@ import struct
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class BaseClient: class BaseClient:
def __init__(self, socket: socket.socket, ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.socket = socket self.server = server
self.ip = ip self.ip = ip
self.port = port self.port = port
@@ -14,3 +15,7 @@ class BaseClient:
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
async def send(self, packet: bytes):
sock: socket.socket = self.server.socket
sock.sendto(packet, (self.ip, self.port))

53
sampy/client/client.py Normal file
View File

@@ -0,0 +1,53 @@
import struct
import socket
import asyncio
from time import time
from . import base
from . import query
from . import player
STATE_UNKNOWN = (0, base.BaseClient)
STATE_QUERY = (1, query.QueryClient)
STATE_PLAYER = (2, player.PlayerClient)
TIMEOUT = 10 # assume connection is closed after 10 seconds of inactivity (change this to a higher value so you dont timeout while debugging might be a good idea)
class Client:
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
self.server = server
self.ip = ip
self.port = port
self.set_state(STATE_UNKNOWN)
self.last_active = time()
self.keep_alive_task = asyncio.create_task(self.keep_alive())
self.connected = True # keep_alive will set this to False if connection has not been interacted with for a while (allowing server loop to remove their reference)
def set_state(self, state: tuple):
# self.keep_alive_task.cancel()
self.state = state
self.client = self.state[1](self.server, self.ip, self.port)
async def on_packet(self, packet: bytes):
self.last_active = time()
if self.state == STATE_UNKNOWN:
# We are currently unaware if this is a player client or query client, but we got a packet that will be our check to know
if packet.startswith(b"SAMP"):
self.set_state(STATE_QUERY)
else:
self.set_state(STATE_PLAYER)
await self.client.on_packet(packet)
async def keep_alive(self): # Maybe bad name for this method as it rather checks if connection is dropped
while True:
timestamp = time()
if self.last_active + TIMEOUT - timestamp < 0:
self.connected = False
return
await asyncio.sleep(self.last_active + TIMEOUT - timestamp)

View File

@@ -1,13 +1,190 @@
import socket import socket
import struct
from . import base from time import time_ns
import logging from .base import BaseClient
from ..raknet.bitstream import Bitstream
from ..helpers import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class PlayerClient(base.BaseClient): STATE_CONNECTING = 0
def __init__(self, socket: socket.socket, ip: str, port: int): STATE_CONNECTED = 1
super().__init__(socket, ip, port)
class PlayerClient(BaseClient):
ranges = []
resend_list = dict()
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port)
logger.debug("Client resolved to PlayerClient")
# TODO: check if banned and handle here
self.state = STATE_CONNECTING
# TODO: Make this static
self.handlers = {
"default": {
"default": self.on_unimplemented_state
},
STATE_CONNECTING: {
"default": self.on_unexpected,
0x18: self.on_connection_request
},
STATE_CONNECTED: {
"default": self.on_player_packet
}
}
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
#try:
packet = self.server.compressor.decompress(packet)
current_handlers = self.handlers.get(self.state, self.handlers["default"])
await current_handlers.get(packet[0], current_handlers["default"])(packet)
#except Exception as err:
#logger.error(err)
async def on_unexpected(self, packet: bytes):
logger.warning("Received unexpected packet: %s" % packet)
async def on_unimplemented_state(self, packet: bytes):
logger.warning("Unimplemented state: %d" % self.state)
async def on_unimplemented(self, packet: bytes):
logger.warning("Unimplemented handler for packet: %s" % packet)
async def on_connection_request(self, packet: bytes):
logger.debug("on_connection_request(%s)" % packet)
challenge = (self.ip_uint ^ self.server.config.challenge_short) & 0xFFFF
challenge_solution = challenge ^ 0x6969 # 0x6969 being client_version?
challenge_answer, = struct.unpack_from(b"<H", packet, 1)
if challenge_answer != challenge_solution: # Did not pass challenge
logger.debug("challenge: failed")
await self.send(struct.pack(b"<BH", 0x1A, challenge))
return
logger.debug("challenge: passed")
self.state = STATE_CONNECTED
await self.send(b"\x19\x00") # Challenge passed
async def on_player_packet(self, packet: bytes):
bitstream = Bitstream(packet)
time = time_ns()
_, has_acks = bitstream.read_bit()
if has_acks:
#logger.debug("Unfinished code hit; has_acks: True")
await self.handle_acknowledgement(bitstream, time)
#return
handled = await self.handle_internal_packet(bitstream)
if not handled:
logger.debug("Internal packet were not handled")
async def handle_acknowledgement(self, bitstream: Bitstream, time: int) -> bool:
if not self.deserialize(bitstream):
return False
for i in range(len(self.ranges)):
min, max = self.ranges[i][0], self.ranges[i][1]
if min > max:
return False
for i in range(min, max + 1, 1):
acked_histogram_counter = self.remove_packet_from_resend_list_and_delete_older_reliable_sequence()
def deserialize(self, bitstream: Bitstream): # TODO: Something weird with range thing DS_RangeList.h@L92
self.ranges.clear()
success, count = bitstream.read_compressed(0x10, True)
if not success:
return False
count, = struct.unpack(b"<H", count)
min, max = 0, 0
for i in range(count):
_, max_equal_to_min = bitstream.read_bit()
success, min = bitstream.read_bits(0x10)
if not success:
return False
min, = struct.unpack(b"<H", min)
if not max_equal_to_min:
success, max = bitstream.read_bits(0x10)
if not success:
return False
max, = struct.unpack(b"<H", max)
if max < min:
return False
else:
max = min
self.ranges.append((min, max))
return True
def remove_packet_from_resend_list_and_delete_older_reliable_sequence(self, message_number: int, time: int):
return 0 # TODO
async def handle_internal_packet(self, bitstream: Bitstream) -> bool:
if bitstream.length - bitstream.offset < 0x10:
return False
_, message_number = bitstream.read_bits(0x10)
_, reliability = bitstream.read_bits(0x04)
if reliability in (7, 9, 10):
success, ordering_channel = bitstream.read_bits(0x05)
if not success:
return False
success, ordering_index = bitstream.read_bits(0x10)
if not success:
return False
_, is_split_packet = bitstream.read_bit()
if is_split_packet:
logger.warning("Skipping split packet")
return
# TODO: set global split_packet index and count back to 0
# Something I dont understand yet
# TODO: ReadCompressed
#bitstream.offset += 1
success, data_bit_length = bitstream.read_compressed(0x10, True)
if not success:
return False
#_, unknown = bitstream.read(0x01)
#_, bit_length = bitstream.read(0x08)
##
#logger.debug("bit_length: %d" % bit_length)
#logger.debug("bitstream: %s" % bitstream)
data_bit_length, = struct.unpack(b"<H", data_bit_length)
# TODO: ReadAlignedBytes
#_, data = bitstream.read(bit_length)
_, data = bitstream.read_aligned_bytes((data_bit_length + 7) >> 3)
logger.debug("data: %s" % data)
return await self.handle_packet(data)
async def handle_packet(self, packet: bytes):
if packet[0] == 0x0B: # ID_CONNECTION_REQUEST
pass # TODO: Send ack and data after
return False

View File

@@ -1,13 +1,119 @@
import socket import socket
import struct
from . import base from .base import BaseClient
from ..shared import glob
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class QueryClient(base.BaseClient):
def __init__(self, socket: socket.socket, ip: str, port: int): class QueryClient(BaseClient):
super().__init__(socket, ip, port) def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port)
logger.debug("Client resolved to QueryClient")
self.handlers = {
b"i": self.query_i,
b"r": self.query_r,
b"c": self.query_c,
b"d": self.query_d,
b"p": self.query_p,
b"x": self.query_x
}
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
if len(packet) <= 10: # Invalid
return
if packet[10:11] in self.handlers:
packet = await self.handlers[packet[10:11]](packet)
if len(packet): # Send packet back if not 0
await self.send(packet)
async def query_i(self, packet: bytes) -> bytes:
len_hostname = len(self.server.config.hostname)
len_mode = len(self.server.config.mode)
len_language = len(self.server.config.language)
return packet + struct.pack(b"<?HHI%dsI%dsI%ds" % (len_hostname, len_mode, len_language),
len(self.server.config.password) != 0,
len(await self.server.get_online_players()),
self.server.config.max_players,
len_hostname,
self.server.config.hostname.encode(),
len_mode,
self.server.config.mode.encode(),
len_language,
self.server.config.language.encode()
)
async def query_r(self, packet: bytes) -> bytes:
data = []
rules = await self.server.get_rules()
data.append(len(rules))
for k, v in rules.items():
data += [
len(k), k,
len(v), v
]
return packet + struct.pack(b"<H" + (b"B%dsB%ds" * data[0]) % tuple(len(y) for x in rules.items() for y in x), *data)
async def query_c(self, packet: bytes) -> bytes:
data = []
scores = await self.server.get_players_scores()
data.append(len(scores))
for k, v in scores.items():
data += [
len(k), k,
v
]
return packet + struct.pack(b"<H" + (b"B%dsI" * data[0]) % tuple(len(x) for x in scores.keys()), *data)
async def query_d(self, packet: bytes) -> bytes:
data = []
players = await self.server.get_online_players()
data.append(len(players))
for p in players:
data += [
p["id"],
len(p["nick"]), p["nick"],
p["score"],
p["ping"]
]
return packet + struct.pack(b"<H" + (b"BB%dsII" * data[0]) % tuple(len(p["nick"]) for p in players), *data)
async def query_p(self, packet: bytes) -> bytes:
return packet
async def query_x(self, packet: bytes) -> bytes:
len_pswd, = struct.unpack_from(b"<H", packet, 11)
pswd, len_cmd = struct.unpack_from(b"<%dsH" % len_pswd, packet, 13)
cmd = struct.unpack_from(b"<%ds" % len_cmd, packet, 15 + len_pswd)
if len(self.server.config.rcon_password) == 0:
msg = b"Remote Console is not enabled on this server."
elif self.server.config.rcon_password.encode() != pswd:
msg = b"Invalid RCON password."
logger.warning("BAD RCON ATTEMPT BY: %s:%d" % (self.ip, self.port))
else:
# TODO: Add rcon client to command stdouts
return b"" # No response as all is ok
return packet[:11] + struct.pack(b"<H%ds" % len(msg), len(msg), msg)

View File

@@ -3,7 +3,6 @@
"logging": { "logging": {
"filename": "", "filename": "",
"level": "DEBUG", "level": "DEBUG",
"format": "[%(name)s:L%(lineno)02d %(levelname)s] %(message)s",
"datefmt": "%d-%b-%y %H:%M:%S" "datefmt": "%d-%b-%y %H:%M:%S"
} }
}, },

View File

@@ -1,21 +1,21 @@
import asyncio import asyncio
from threading import Thread from threading import Thread
from .struct.server import Server as struct from .struct.server import ServerConfig
from . import server from .server import Server
class Environment(Thread): class Environment(Thread):
def __init__(self, config: struct): def __init__(self, config: ServerConfig):
super().__init__() super().__init__()
self.daemon = True self.daemon = True
self.event_loop = asyncio.get_event_loop() self.event_loop = asyncio.get_event_loop()
self.config = config self.config = config
self.server = server.Server(self.config) self.server = Server(self.config)
def command(self, cmd: str): def command(self, cmd: str):
self.event_loop.create_task(self.server.on_command(cmd)) self.event_loop.create_task(self.server.on_command(cmd))
def run(self): def run(self):
self.event_loop.run_until_complete(self.server.main()) self.event_loop.run_until_complete(self.server.main())
print("Ended?")

View File

@@ -0,0 +1 @@
from . import logging

62
sampy/helpers/logging.py Normal file
View File

@@ -0,0 +1,62 @@
import sys
import logging
from logging import *
if sys.platform == 'win32':
import colorama
colorama.init()
FORMAT_STR_NORMAL = "%(levelname)s \x1b[33;1m%(asctime)s.%(msecs)03d \x1b[30;1m|\x1b[0m %(message)s"
COLOR_RESET = 0
COLOR_BLACK = 30
COLOR_RED = 31
COLOR_GREEN = 32
COLOR_YELLOW = 33
COLOR_BLUE = 34
COLOR_PURPLE = 35
COLOR_CYAN = 36
COLOR_GRAY = 90
COLOR_PINK = 95
COLOR_PREFIX = "\x1b[%dm"
def color(color_code: int) -> str:
return COLOR_PREFIX % color_code
def init():
from ..shared import glob
COLORS = {
logging.CRITICAL: "31",
logging.FATAL: "31",
logging.ERROR: "31",
logging.WARNING: "33",
logging.INFO: "32",
logging.DEBUG: "35",
logging.NOTSET: "37",
}
for k,v in COLORS.items():
logging.addLevelName(k, "\x1b[%sm%s\x1b[0m" % (v, " " * (8 - len(logging.getLevelName(k))) + logging.getLevelName(k)))
conf = glob.conf["logging"]
default_logging_fallback = False
if type(conf["level"]) is not int:
try:
conf["level"] = getattr(logging, conf["level"])
except:
conf["level"] = logging.INFO
default_logging_fallback = True
logging_handlers = [logging.StreamHandler(sys.stdout)]
if len(conf["filename"]):
logging_handlers.append(logging.FileHandler(conf["filename"]))
del conf["filename"]
logging.basicConfig(**conf, format=FORMAT_STR_NORMAL, handlers = logging_handlers)
if default_logging_fallback:
logging.warning("Invalid logging value in config! Defaulting to logging level INFO.")
logging.info("Logging level: %d" % conf["level"])
logging.debug("Logger configured")

1
sampy/raknet/__init__.py Normal file
View File

@@ -0,0 +1 @@
from . import bitstream

388
sampy/raknet/bitstream.py Normal file
View File

@@ -0,0 +1,388 @@
from __future__ import annotations
import ctypes as c
from array import array
from typing import Tuple, Union
from ..helpers import logging
logger = logging.getLogger(__name__)
DEBUG = (logger.root.level & logging.DEBUG) == 10
if DEBUG:
import inspect
BITS_TO_BYTES = lambda x: (((x)+7)>>3)
BYTES_TO_BITS = lambda x: ((x)<<3)
debug_wrapper_depth = 0
ignored_bits = []
class Bitstream:
def __init__(self, data: bytes = b""):
self._offset = 0
self._buffer = bytearray(data) # array("B", data)
def debug_wrapper(func):
def wrapper(self, *args, **kwargs):
if not DEBUG:
return func(self, *args, **kwargs)
global debug_wrapper_depth
#global ignored_bits
ignored_bits.clear()
curframe = inspect.currentframe()
calframe = inspect.getouterframes(curframe, 2)
#print(calframe)
copy = self.clone()
header_func = "%s%s" % (func.__name__, (*args, *kwargs))
header = " {BLUE}{header_func}{SPACE_FUNC}{CYAN}{caller}{SPACE_CALLER}{GREEN}{file}{YELLOW}@L{lineno}{RESET}".format(
header_func = header_func,
caller = calframe[1].function,
file = calframe[1].filename.split("\\")[-1],
lineno = calframe[1].lineno,
CYAN = logging.color(logging.COLOR_CYAN),
BLUE = logging.color(logging.COLOR_BLUE),
GREEN = logging.color(logging.COLOR_GREEN),
YELLOW = logging.color(logging.COLOR_YELLOW),
RESET = logging.color(logging.COLOR_RESET),
SPACE_FUNC = " " * (28 - len(header_func)),
SPACE_CALLER = " " * (32 - len(calframe[1].function)),
)
prefix = "%s%s%s" % (
logging.color(logging.COLOR_RED),
">" * debug_wrapper_depth,
logging.color(logging.COLOR_RESET)
)
logger.debug(prefix + header)
debug_wrapper_depth += 1
result = func(self, *args, **kwargs)
edits = self.debug_edits(copy)
debug_wrapper_depth -= 1
logger.debug(edits)
return result
return wrapper
@property
def offset(self) -> int:
return self._offset
@property
def length(self) -> int:
return len(self._buffer) << 3
def clone(self) -> Bitstream:
copy = Bitstream(self._buffer)
copy._offset = self._offset
return copy
def _can_access_bits(self, bits_to_access: int) -> bool:
return self._offset + bits_to_access <= len(self._buffer) << 3
def add_bits(self, bit_length: int):
if bit_length <= 0:
return
self._buffer.extend(b"\x00" * BITS_TO_BYTES(bit_length))
def align_read_to_byte_boundary(self):
if DEBUG:
ignored_from = self._offset
if self._offset:
self._offset += 8 - (((self._offset - 1) & 7) + 1)
if DEBUG:
ignored_bits.append((ignored_from, self._offset))
@debug_wrapper
def write_bit(self, value: bool) -> bool:
if not self._can_access_bits(1):
return False
mask = (1 << (7 - (self._offset % 8)))
if value:
self._buffer[self._offset >> 3] |= mask
else:
self._buffer[self._offset >> 3] &= ~mask
self._offset += 1
return True
@debug_wrapper
def read_bit(self) -> Tuple[bool, bool]:
if not self._can_access_bits(1):
return False, False
mask = (1 << (7 - (self._offset % 8)))
value = self._buffer[self._offset >> 3] & mask
self._offset += 1
return True, value > 0
@debug_wrapper
def write(self, value: bytes, bit_length: int) -> bool:
if not self._can_access_bits(bit_length):
return False
if len(value) << 3 < bit_length:
return False
byte_from = self._offset >> 3
byte_to = (self._offset + bit_length + 7) >> 3
bits_written = 0
for byte_index in range(byte_from, byte_to, 1):
byte = self._buffer[byte_index]
for bit_index in range(self._offset % 8, 8, 1):
mask = 1 << (7 - bit_index)
bit = value[bits_written >> 3] & (1 << ((7 - bits_written) % 8))
if bit:
byte |= mask
else:
byte &= ~mask
self._offset += 1
bits_written += 1
if bits_written >= bit_length:
break
self._buffer[byte_index] = byte
return True
@debug_wrapper
def read_int(self, bit_length: int) -> Tuple[bool, int]:
if not self._can_access_bits(bit_length):
return False, 0
byte_from = self._offset >> 3
byte_to = (self._offset + bit_length + 7) >> 3
# Since python doesnt have a max number size,
# we can read the bits we need and store them into a number,
# then convert the number to bytes (as they are all in the same for python)
value = 0
for byte_index in range(byte_from, byte_to, 1):
for bit_index in range(self._offset % 8, 8, 1):
mask = 1 << (7 - bit_index)
bit = self._buffer[self._offset >> 3] & (1 << ((7 - self._offset) % 8))
value = (value << 1) + bit
self._offset += 1
if self._offset >= bit_length:
break
#if self._offset % 8 == 0:
#value +=
return True, value
def read(self, bit_length: int) -> Tuple[bool, bytes]:
success, value = self.read_int(bit_length)
return success, value.to_bytes((bit_length + 7) >> 3, "big") # bytes(value)
@debug_wrapper
def write_compressed(self, data: bytes, bit_length: int, unsigned: bool = True) -> bool:
current_byte = (bit_length >> 3) - 1
byte_match = 0 if unsigned else 0xFF
while current_byte > 0:
if data[current_byte] == byte_match:
self.write_bit(True)
else:
self.write_bit(False)
self.write_bits(data, (current_byte + 1) << 3, True)
return True
if (unsigned and data[current_byte] & 0xF0 == 0x00) or (not unsigned and data[current_byte] & 0xF0 == 0xF0):
self.write_bit(True)
self.write_bits(data[current_byte:], 4, True)
else:
self.write_bit(False)
self.write_bits(data[current_byte:], 8, True)
return True
@debug_wrapper
def read_compressed(self, bit_length: int, unsigned: bool = True) -> Tuple[bool, bytes]:
current_byte = (bit_length >> 3) - 1
out = bytearray(current_byte + 1)
byte_match = 0 if unsigned else 0xFF
half_byte_match = 0 if unsigned else 0xF0
while current_byte > 0:
success, bit = self.read_bit()
if not success: # Cannot read the bit (end of stream)
return False, out
if bit:
out[current_byte] = byte_match
current_byte -= 1
else:
success, data = self.read_bits((current_byte + 1) << 3, True)
out[:len(data)] = data
if not success:
return False, out
return True, out
if self.offset + 1 > self.length:
return False, out
success, bit = self.read_bit()
if not success: # Cannot read the bit (end of stream)
return False, out
if bit:
success, data = self.read_bits(4, True)
out[current_byte:((current_byte + 4) + 7) >> 3] = data
if not success:
return False, out
out[current_byte] |= half_byte_match # Maybe recheck this in BitStream.cpp@L617; We have to set the high 4 bits since these are set to 0 by ReadBits
else:
success, data = self.read_bits(8, True)
out[current_byte:current_byte + 8] = data
if not success:
return False, out
return True, out
@debug_wrapper
def write_bits(self, data: bytes, bit_length: int, align_bits_to_right: bool = True) -> bool:
if bit_length <= 0:
return False
self.add_bits(bit_length) # TODO: Check if needed
bits_used_mod8 = self.offset & 7
offset = 0
while bit_length > 0:
byte = data[offset]
if bit_length < 8 and align_bits_to_right:
byte <<= 8 - bit_length
if bits_used_mod8 == 0:
self._buffer[self.offset >> 3] = byte
else:
self._buffer[self.offset >> 3] |= byte >> bits_used_mod8
if 8 - bits_used_mod8 < 8 and 8 - bits_used_mod8 < bit_length: # Eh.. why not bits_used_mod8 != 0?
self._buffer[(self.offset >> 3) + 1] = byte << (8 - bits_used_mod8)
if bit_length >= 8:
self._offset += 8
else:
self._offset += bit_length
bit_length -= 8
offset += 1
return True
@debug_wrapper
def read_bits(self, bit_length: int, align_bits_to_right: bool = True) -> Tuple[bool, bytes]:
if bit_length <= 0:
return False, b""
if not self._can_access_bits(bit_length):
return False, b""
out = bytearray(BITS_TO_BYTES(bit_length))
read_offset_mod8 = self._offset & 7
offset = 0
while bit_length > 0:
out[offset] |= (self._buffer[self._offset >> 3] << read_offset_mod8) & 255
if read_offset_mod8 > 0 and bit_length > 8 - read_offset_mod8:
out[offset] |= self._buffer[(self.offset >> 3) + 1] >> (8 - read_offset_mod8)
bit_length -= 8
if bit_length < 0:
if align_bits_to_right:
out[offset] >>= -bit_length
self._offset += 8 + bit_length
else:
self._offset += 8
offset += 1
return True, out
@debug_wrapper
def read_aligned_bytes(self, byte_length: int) -> Tuple[bool, bytes]:
if byte_length <= 0:
return False, b""
self.align_read_to_byte_boundary()
if self.offset + (byte_length << 3) > (len(self._buffer) << 3):
return False, b""
out = self._buffer[(self.offset >> 3):(self.offset >> 3) + byte_length]
self._offset += byte_length << 3
return True, out
def pretty(self) -> str:
b = bytearray(" " + " ".join(" ".join(format(c, "08b")) for c in self._buffer) + " ", "ascii")
m = self._offset * 2 + (self._offset >> 3) - (self._offset // (max(1, len(self._buffer)) * 8))
b[m] = 124
return b.decode()
def debug_edits(self, prev_state: Bitstream) -> str:
#logger.debug(self.pretty())
bits = [list(format(c, "08b")) for c in self._buffer]
prev_bits = [list(format(c, "08b")) for c in prev_state._buffer]
bits.append([])
start = prev_state._offset
end = self._offset
edits = {start: "P", end: "R"}
for s, e in ignored_bits:
t = [x for x in edits.keys() if x <= s]
if len(t):
edits[e] = edits[t[-1]]
else:
edits[e] = "R"
edits[s] = "S"
edits = sorted(edits.items())
edits.reverse()
for k, v in edits:
bits[k // 8][k % 8:k % 8] = v
#print(bits)
bits = " ".join("".join([y + " " if y in "01" else y for y in x]).strip() for x in bits)
bits = bits.replace("R", logging.color(logging.COLOR_RESET))
bits = bits.replace("P", logging.color(logging.COLOR_PINK))
bits = bits.replace("S", logging.color(logging.COLOR_RED))
return bits
def __repr__(self) -> str:
return "<Bitstream addr:0x%012x offset:%d len:%d>" % (id(self), self._offset, len(self._buffer) << 3)

View File

@@ -1,40 +0,0 @@
import socket
import asyncio
from select import select # This is straight up magic
from .struct.server import Server as struct
from .client import Client
import logging
logger = logging.getLogger(__name__)
class Server:
def __init__(self, config: struct):
self.config = config
self.clients = {}
async def create_socket(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((self.config.host, self.config.port))
self.socket.setblocking(0) # TODO: Check if needed? I dont fully understand this "feature"
logger.debug("Socket created")
async def on_command(self, cmd: str):
logger.debug("on_command(%s)" % cmd)
async def main(self):
await self.create_socket()
while True:
(incomming, _, _) = select([self.socket], [], [], 0) # How this works is beyond me, but this sets `incomming` to be true~y if socket has awaiting data
if incomming:
data, addr = self.socket.recvfrom(0xFFFF)
if addr not in self.clients:
ip, port = addr
self.clients[addr] = Client(self.socket, ip, port)
await self.clients[addr].on_packet(data)
await asyncio.sleep(0)

4
sampy/server/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
from . import compression
from . import server
from .server import Server

View File

@@ -0,0 +1,61 @@
from ..shared import glob
from ..struct.server import ServerConfig
from typing import Tuple
import logging
logger = logging.getLogger(__name__)
# Found @ addr 0x004C88E0
LOOKUP_TABLE = b"\xb4b\x07\xe5\x9d\xafc\xdd\xe3\xd0\xcc\xfe\xdc\xdbk.j@\xabG\xc9\xd1S\xd5 \x91\xa5\x0eJ\xdf\x18\x89\xfdo%\x12\xb7\x13w\x00e6mI\xecW*\xa9\x11_\xfax\x95\xa4\xbd\x1e\xd9yD\xcd\xde\x81\xeb\t>\xf6\xee\xda\x7f\xa3\x1a\xa7-\xa6\xad\xc1F\x93\xd2\x1b\x9c\xaa\xd7NKML\xf3\xb84\xc0\xca\x88\xf4\x94\xcb\x0490\x82\xd6s\xb0\xbf\"\x01AnH,\xa8u\xb1\n\xae\x9f\'\x80\x10\xce\xf0)(\x85\r\x05\xf75\xbb\xbc\x15\x06\xf5`q\x03\x1f\xeaZ3\x92\x8d\xe7\x90[\xe9\xcf\x9e\xd3]\xed1\x1c\x0bR\x16Q\x0f\x86\xc5h\x9b!\x0c\x8bB\x87\xffO\xbe\xc8\xe8\xc7\xd4z\xe0U/\x8a\x8e\xba\x987\xe4\xb28\xa1\xb62\x83:{\x84<a\xfb\x8c\x14=C;\x1d\xc3\xa2\x96\xb3\xf8\xc4\xf2&+\xd8|\xfc#$f\xefidPTY\xf1\xa0t\xac\xc6}\xb5\xe6\xe2\xc2~g\x17^\xe1\xb9?lp\x08\x99EVv\xf9\x9a\x97\x19r\\\x02\x8fX"
class Compressor:
def __init__(self, config: ServerConfig):
self.config = config
def decompress(self, bytestream: bytes) -> bytes:
"""Decompress client packet.
This is actually a deobfuscation as there is no compression involved anymore
as zlib was removed and just swapped with this implementation after the leak.
Arguments:
bytestream {bytes} -- Bytes sent by client
"""
checksum, data = bytestream[0], bytearray(bytestream[1:])
data = self.xor_every_other_byte(self.get_port_xor_key(), data)
data = self.run_though_lookup_table(data)
if checksum != self.calc_checksum(data):
logger.error("Checksum failed!")
raise Exception("Checksum fail")
logger.debug("decompress -> %s" % data)
return bytes(data)
def xor_every_other_byte(self, xor: int, bytestream: bytearray) -> bytearray:
for i in range(1, len(bytestream), 2):
bytestream[i] ^= xor
return bytestream
def run_though_lookup_table(self, bytestream: bytearray) -> bytearray:
return bytes(LOOKUP_TABLE[b] for b in bytestream)
def calc_checksum(self, bytestream: bytearray) -> int:
checksum = 0
for byte in bytestream:
checksum ^= byte & 0xAA
return checksum
def get_port_xor_key(self) -> int:
return (self.config.port ^ 0xCCCC) & 0xFF
class StringCompressor:
def __init__(self):
pass
def decode_string(self, data: bytes) -> Tuple[str, int]:
pass

65
sampy/server/server.py Normal file
View File

@@ -0,0 +1,65 @@
import socket
import asyncio
from select import select # This is straight up magic
from ..struct.server import ServerConfig
from ..client import Client
from .compression import Compressor
import logging
logger = logging.getLogger(__name__)
class Server:
def __init__(self, config: ServerConfig):
self.config = config
self.clients = {}
self.rcon_clients = {}
self.compressor = Compressor(self.config)
async def create_socket(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((self.config.host, self.config.port))
self.socket.setblocking(0)
logger.debug("Socket created")
async def on_command(self, cmd: str):
logger.debug("on_command(%s)" % cmd)
# TODO: When commands return a reponse we also want to forward this to potential rcon clients
async def get_online_players(self): # TODO: Get data from server's client objects
return [
{"nick": b"Sunpy", "score": 64, "ping": 8, "id": 1} # replace id with function to get player's id
]
async def get_rules(self): # TODO
return {b"Rule name sample": b"Rule value", b"weburl": b"https://git.osufx.com/Sunpy/sampy"}
async def get_players_scores(self): # TODO
return {b"Sunpy": 64, b"username": 123}
async def main(self):
await self.create_socket()
while True:
(incomming, _, _) = select([self.socket], [], [], 0) # How this works is beyond me, but this sets `incomming` to be true~y if socket has awaiting data
if incomming:
data, addr = self.socket.recvfrom(0xFFFF)
if addr not in self.clients:
ip, port = addr
self.clients[addr] = Client(self, ip, port)
await self.clients[addr].on_packet(data)
disconnected = [c for c in self.clients.values() if c.connected == False]
for c in disconnected: # Remove dead connections
addr = (c.ip, c.port)
if addr in self.clients:
del self.clients[addr]
logger.debug("free(%s)" % c)
await asyncio.sleep(0)

View File

@@ -2,7 +2,8 @@ import os
import sys import sys
import json import json
import shutil import shutil
import logging
from ..helpers import logging
if not os.path.isfile("config.json"): if not os.path.isfile("config.json"):
shutil.copyfile(os.path.join(*__package__.split(".")[:-1], "default_config.json"), "config.json") shutil.copyfile(os.path.join(*__package__.split(".")[:-1], "default_config.json"), "config.json")
@@ -10,28 +11,8 @@ if not os.path.isfile("config.json"):
with open("config.json", "r") as f: with open("config.json", "r") as f:
config = json.load(f) config = json.load(f)
# Setup logger # aliases
conf_log = config["sampy"]["logging"] # alias conf = config["sampy"]
## fix for logging level conf_log = conf["logging"]
default_logging_fallback = False
if type(conf_log["level"]) is not int:
try:
conf_log["level"] = getattr(logging, conf_log["level"])
except:
conf_log["level"] = logging.INFO
default_logging_fallback = True
## logging settings logging.init()
logging_handlers = [logging.StreamHandler(sys.stdout)]
if len(conf_log["filename"]):
logging_handlers.append(logging.FileHandler(conf_log["filename"]))
del conf_log["filename"]
logging.basicConfig(**conf_log, handlers = logging_handlers)
logger = logging.getLogger(__name__)
logger.debug("Configured logger")
if default_logging_fallback:
logger.warning("Invalid logging value in config! Defaulting to logging level INFO.")
logger.info("Logging level: %d" % conf_log["level"])

View File

@@ -1 +1,2 @@
from . import server from . import server
from . import packet

33
sampy/struct/packet.py Normal file
View File

@@ -0,0 +1,33 @@
class InternalPacket:
def __init__(self):
self.message_number = message_number # ushort
self.pack_number = pack_number # uint
self.priority = priority # int? this could be a byte (actually 2 bits is enough)
self.reliability = reliability # int? this could be a byte (actually 3 bits is enough)
self.ordering_channel = ordering_channel # uchar
self.ordering_index = ordering_index # ushort
self.split_packet_id = split_packet_id # ushort
self.split_packet_index = split_packet_index # uint
self.split_packet_count = split_packet_count # uint
self.creation_time = creation_time # long long
self.next_action_time = next_action_time # long long
self.data_bit_length = data_bit_length # uint
self.data = data # char*
self.histogram_marker = histogram_marker # uint
def get_bitstream_header_length(self) -> int:
bit_length = 32 # 2 * 2 * 8
bit_length += 3 # TODO: merge ^
if self.reliability in [1, 3, 4]:
bit_length += 5
bit_length += 16 # 2 * 8 TODO: merge ^
bit_length += 1 # TODO: merge ^
if self.split_packet_count > 0:
bit_length += 80 # (2 + 4 * 2) * 8
bit_length += 16 # TODO: merge ^
return bit_length

View File

@@ -1,9 +1,10 @@
from random import randint from random import randint
class Server: class ServerConfig:
def __init__(self, def __init__(self,
host: str, port: int, host: str, port: int,
hostname: str, password: str, hostname: str, password: str,
rcon_password: str,
max_players: int, max_players: int,
mode: str, language: str): mode: str, language: str):
self.host = host self.host = host
@@ -12,8 +13,10 @@ class Server:
self.hostname = hostname self.hostname = hostname
self.password = password self.password = password
self.rcon_password = rcon_password
self.max_players = max_players self.max_players = max_players
self.mode = mode self.mode = mode
self.language = language self.language = language

128
test.py Normal file
View File

@@ -0,0 +1,128 @@
from sampy.raknet.bitstream import Bitstream
from sampy.client.player import PlayerClient
import struct
class Packet:
def __init__(self, data: bytes, is_client: bool):
self.data = data
self.is_client = is_client
self.bitstream = Bitstream(bytearray.fromhex(self.data))
PACKETS = [
Packet("18 69 69", True),
Packet("1a 18 be", False),
Packet("18 71 d7", True),
Packet("19 00", False),
Packet("00 00 43 80 0b", True),
Packet("e3 00 00", False),
Packet("00 00 42 98 0c 11 33 30 45 39 33 39 33 33 36 39 42 35 36 38 43 32 00", False)
]
#client = PlayerClient(None, "127.0.0.1", 7777)
#client.state = 1
def pbs(bitstream: Bitstream, prefix: str = ""):
print(prefix, bitstream.pretty())
def deserialize(bitstream: Bitstream): # TODO: Something weird with range thing DS_RangeList.h@L92
success, count = bitstream.read_compressed(0x10, True)
if not success:
return False
count, = struct.unpack(b"<H", count)
min, max = 0, 0
for i in range(count):
_, max_equal_to_min = bitstream.read_bit()
success, min = bitstream.read_bits(0x10)
if not success:
return False
min, = struct.unpack(b"<H", min)
if not max_equal_to_min:
success, max = bitstream.read_bits(0x10)
if not success:
return False
max, = struct.unpack(b"<H", max)
if max < min:
return False
else:
max = min
return True
def on_player_packet(packet: bytes):
bitstream = Bitstream(packet)
pbs(bitstream, "Created")
_, has_acks = bitstream.read_bit()
pbs(bitstream, "has_acks")
if has_acks:
print("has_acks: True")
if not deserialize(bitstream):
return False
return
handled = handle_internal_packet(bitstream)
if not handled:
print("Internal packet were not handled")
def handle_internal_packet(bitstream: Bitstream) -> bool:
if bitstream.length - bitstream.offset < 0x10:
return False
_, message_number = bitstream.read_bits(0x10)
pbs(bitstream, "message_number")
_, reliability = bitstream.read_bits(0x04)
pbs(bitstream, "reliability")
if reliability in (7, 9, 10):
success, ordering_channel = bitstream.read_bits(0x05)
pbs(bitstream, "ordering_channel")
if not success:
return False
success, ordering_index = bitstream.read_bits(0x10)
pbs(bitstream, "ordering_index")
if not success:
return False
_, is_split_packet = bitstream.read_bit()
pbs(bitstream, "is_split_packet")
if is_split_packet:
print("Skipping split packet")
return
# TODO: set global split_packet index and count back to 0
# Something I dont understand yet
# TODO: ReadCompressed
#bitstream.offset += 1
success, data_bit_length = bitstream.read_compressed(0x10, True)
pbs(bitstream, "data_bit_length")
if not success:
return False
#_, unknown = bitstream.read(0x01)
#_, bit_length = bitstream.read(0x08)
##
#logger.debug("bit_length: %d" % bit_length)
#logger.debug("bitstream: %s" % bitstream)
data_bit_length, = struct.unpack(b"<H", data_bit_length)
print("data_bit_length:", data_bit_length)
# TODO: ReadAlignedBytes
#_, data = bitstream.read(bit_length)
_, data = bitstream.read_aligned_bytes((data_bit_length + 7) >> 3)
pbs(bitstream, "data")
print("data: %s" % data)
on_player_packet(PACKETS[-2].bitstream._buffer)