Compare commits
14 Commits
a30b9f1a62
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c258c520a5 | |||
| 4812d9c35d | |||
| 6d9c0805ac | |||
| 4acb1adeb4 | |||
| 59b30ed591 | |||
| f9d8470ee6 | |||
| 483587fd3b | |||
| 1b78fb98bc | |||
| 0cd474601a | |||
| 37e732e19f | |||
| deb4bb7483 | |||
| 3d359a4424 | |||
| 558be67f61 | |||
| cb2d31e033 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,7 +52,6 @@ coverage.xml
|
|||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
|
||||||
local_settings.py
|
local_settings.py
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
123
notes/dumps/1586009002.log
Normal file
123
notes/dumps/1586009002.log
Normal 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
148
notes/dumps/1586009798.log
Normal 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
45
notes/notes.txt
Normal 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
44
notes/packet_writeup.md
Normal 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
12
notes/packetlist.txt
Normal 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
|
||||||
3
notes/reliabilityLayer.txt
Normal file
3
notes/reliabilityLayer.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
0x07 UNRELIABLE_SEQUENCED
|
||||||
|
0x0A RELIABLE_SEQUENCED
|
||||||
|
0x09 RELIABLE_ORDERED
|
||||||
@@ -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
|
||||||
@@ -1,52 +1,6 @@
|
|||||||
import struct
|
|
||||||
import socket
|
|
||||||
import asyncio
|
|
||||||
from time import time
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import struct
|
|||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseClient:
|
class BaseClient:
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
self.server = server
|
self.server = server
|
||||||
|
|||||||
53
sampy/client/client.py
Normal file
53
sampy/client/client.py
Normal 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)
|
||||||
@@ -1,14 +1,190 @@
|
|||||||
import socket
|
import socket
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from time import time_ns
|
||||||
|
|
||||||
from .base import BaseClient
|
from .base import BaseClient
|
||||||
|
from ..raknet.bitstream import Bitstream
|
||||||
|
|
||||||
import logging
|
from ..helpers import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STATE_CONNECTING = 0
|
||||||
|
STATE_CONNECTED = 1
|
||||||
|
|
||||||
|
|
||||||
class PlayerClient(BaseClient):
|
class PlayerClient(BaseClient):
|
||||||
|
ranges = []
|
||||||
|
resend_list = dict()
|
||||||
|
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
super().__init__(server, ip, port)
|
super().__init__(server, ip, port)
|
||||||
logger.debug("Client resolved to PlayerClient")
|
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
|
||||||
@@ -7,6 +7,7 @@ from ..shared import glob
|
|||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class QueryClient(BaseClient):
|
class QueryClient(BaseClient):
|
||||||
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
def __init__(self, server: "__ServerInstance__", ip: str, port: int):
|
||||||
super().__init__(server, ip, port)
|
super().__init__(server, ip, port)
|
||||||
@@ -96,7 +97,7 @@ class QueryClient(BaseClient):
|
|||||||
p["ping"]
|
p["ping"]
|
||||||
]
|
]
|
||||||
|
|
||||||
return packet + struct.pack(b"<H" + (b"cc%dsII" * data[0]) % tuple(len(p["nick"]) for p in players), *data)
|
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:
|
async def query_p(self, packet: bytes) -> bytes:
|
||||||
return packet
|
return packet
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from threading import Thread
|
|||||||
from .struct.server import ServerConfig
|
from .struct.server import ServerConfig
|
||||||
from .server import Server
|
from .server import Server
|
||||||
|
|
||||||
|
|
||||||
class Environment(Thread):
|
class Environment(Thread):
|
||||||
def __init__(self, config: ServerConfig):
|
def __init__(self, config: ServerConfig):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
1
sampy/helpers/__init__.py
Normal file
1
sampy/helpers/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import logging
|
||||||
62
sampy/helpers/logging.py
Normal file
62
sampy/helpers/logging.py
Normal 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
1
sampy/raknet/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import bitstream
|
||||||
388
sampy/raknet/bitstream.py
Normal file
388
sampy/raknet/bitstream.py
Normal 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)
|
||||||
4
sampy/server/__init__.py
Normal file
4
sampy/server/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from . import compression
|
||||||
|
from . import server
|
||||||
|
|
||||||
|
from .server import Server
|
||||||
61
sampy/server/compression.py
Normal file
61
sampy/server/compression.py
Normal 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
|
||||||
@@ -2,23 +2,28 @@ import socket
|
|||||||
import asyncio
|
import asyncio
|
||||||
from select import select # This is straight up magic
|
from select import select # This is straight up magic
|
||||||
|
|
||||||
from .struct.server import ServerConfig
|
from ..struct.server import ServerConfig
|
||||||
from .client import Client
|
from ..client import Client
|
||||||
|
|
||||||
|
from .compression import Compressor
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self, config: ServerConfig):
|
def __init__(self, config: ServerConfig):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.clients = {}
|
self.clients = {}
|
||||||
self.rcon_clients = {}
|
self.rcon_clients = {}
|
||||||
|
|
||||||
|
self.compressor = Compressor(self.config)
|
||||||
|
|
||||||
async def create_socket(self):
|
async def create_socket(self):
|
||||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
self.socket.bind((self.config.host, self.config.port))
|
self.socket.bind((self.config.host, self.config.port))
|
||||||
self.socket.setblocking(0) # TODO: Check if needed? I dont fully understand this "feature"
|
self.socket.setblocking(0)
|
||||||
logger.debug("Socket created")
|
logger.debug("Socket created")
|
||||||
|
|
||||||
async def on_command(self, cmd: str):
|
async def on_command(self, cmd: str):
|
||||||
@@ -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")
|
||||||
@@ -14,27 +15,4 @@ with open("config.json", "r") as f:
|
|||||||
conf = config["sampy"]
|
conf = config["sampy"]
|
||||||
conf_log = conf["logging"]
|
conf_log = conf["logging"]
|
||||||
|
|
||||||
# Setup logger
|
logging.init()
|
||||||
## fix for logging level
|
|
||||||
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_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"])
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
from . import server
|
from . import server
|
||||||
|
from . import packet
|
||||||
33
sampy/struct/packet.py
Normal file
33
sampy/struct/packet.py
Normal 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
|
||||||
@@ -16,7 +16,7 @@ class ServerConfig:
|
|||||||
self.rcon_password = rcon_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
128
test.py
Normal 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)
|
||||||
Reference in New Issue
Block a user