Final commit before archive

This commit is contained in:
2020-03-29 18:45:38 +02:00
parent 2166a30aa9
commit 0e2bc4f886
31 changed files with 74 additions and 54 deletions

7
sampy/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
from . import handlers
from . import helpers
from . import network
from . import raknet
from . import glob
from . import client
from . import server

80
sampy/client.py Normal file
View File

@@ -0,0 +1,80 @@
import socket
import logging
import struct
from .network.compression import uncompress
from .handlers import serverQueryHandler, mainHandler
from .helpers.byteFormater import readable_bytes
from .server import Server
from . import glob
if glob.config["debug"]:
from importlib import reload
logger = logging.getLogger(__name__)
class Client:
BUFF_SIZE = 65535
HANDLERS = {
b"SAMP": serverQueryHandler.handle
}
def __init__(self, server, addr: "Address"):
self.server = server
self.ip_uint = struct.unpack(b"<I", bytes(int(x) for x in addr[0].split(".")))[0]
self.addr = addr
self.state = 0
self.__writebuffer = bytearray()
def handle_data(self, data: bytes):
found = False
for pattern, handle in self.HANDLERS.items(): # SAMP query
if data.startswith(pattern):
queue = handle(self, data)
if queue:
self.write(queue)
found = True
if not found:
# deobfuscate
try:
data: bytearray = uncompress(data)
logger.debug("[%s] -> %s" % (self.addr, readable_bytes(data)))
queue = mainHandler.handle(self, data)
except Exception as err:
logger.error(err)
finally:
if queue:
self.write(queue)
def write(self, data: bytes):
if type(data) == int:
self.__writebuffer.append(data)
elif type(data) == list:
for item in data:
self.write(item)
else:
self.__writebuffer += data
def get_buffer_size(self) -> int:
return len(self.__writebuffer)
def flush(self):
if not self.get_buffer_size():
return
logger.debug("[%s] <- %s" % (self.addr, readable_bytes(self.__writebuffer)))
self.server.socket.sendto(self.__writebuffer, self.addr)
self.__writebuffer.clear()
def send_immediate(self, data: bytes):
logger.debug("[%s] <- %s" % (self.addr, readable_bytes(data)))
self.server.socket.sendto(data, self.addr)
def restart():
reload(serverQueryHandler)
reload(mainHandler)
mainHandler.restart()

46
sampy/glob.py Normal file
View File

@@ -0,0 +1,46 @@
import sys
import json
import logging
from random import randint
from .network.enums import types as TYPE
# ---------------------------------------------------------------------------
# public
config = None
server = None
challenge_short = None
# ---------------------------------------------------------------------------
with open("config.json", "r") as f:
config = json.load(f)
# fix for logging level
default_logging_fallback = False
if type(config["logging"]["level"]) is not int:
try:
config["logging"]["level"] = getattr(logging, config["logging"]["level"])
except:
config["logging"]["level"] = logging.INFO
default_logging_fallback = True
# Setup logging settings
logging_handlers = [logging.StreamHandler(sys.stdout)]
if len(config["logging"]["filename"]):
logging_handlers.append(logging.FileHandler(config["logging"]["filename"]))
del config["logging"]["filename"]
logging.basicConfig(**config["logging"], handlers = logging_handlers)
logger = logging.getLogger(__name__)
logger.debug("glob.configured logger")
if default_logging_fallback:
logger.warning("Invalid logging value in config! Defaulting to logging level INFO.")
logger.info("Logging level: %d" % config["logging"]["level"])
# ---------------------------------------------
challenge_short = randint(0, TYPE.MAX_USHORT)
logger.debug("Client challenge set to: %s" % ("0x" if challenge_short & 0x1000 else "0x0") + hex(challenge_short)[2:])

View File

@@ -0,0 +1,5 @@
from . import mainHandler
from . import serverQueryHandler
from . import handshakeHandler
from . import unknownHandler
from . import queueHandler

View File

@@ -0,0 +1,18 @@
import logging
import struct
from ..network.enums import types as TYPE
from .. import glob
logger = logging.getLogger(__name__)
def handle(client: "Client", data: bytes) -> bytes:
challenge = (client.ip_uint ^ glob.challenge_short) & TYPE.MAX_USHORT
challenge_solution = challenge ^ 0x6969 # 0x6969 being client_version?
challenge_answer = struct.unpack_from(b">H", data, 1)[0]
if challenge_answer != challenge_solution:
return struct.pack(b">BH", 0x1A, challenge)
client.state = 1 # CHALLENGE_PASSED
return b"\x19\x00" # 0x19: Valid_challenge; leading 0x00 byte due to some routers shitting itself if only 1 byte is sent/received

View File

@@ -0,0 +1,31 @@
from importlib import reload
import random
import logging
import struct
from . import handshakeHandler
from . import unknownHandler
from . import queueHandler
from ..helpers.byteFormater import readable_bytes
logger = logging.getLogger(__name__)
HANDLERS = {
0x18: handshakeHandler.handle,
0x00: unknownHandler.handle,
0xe3: queueHandler.handle
}
def handle(client: "Client", data: bytes) -> bytes:
out: bytes = HANDLERS.get(data[0], unimplemented)(client, data)
return out
def unimplemented(client: "Client", data: bytes) -> bytes:
logger.debug("[%s] sent unhandled data: %s" % (client.addr, readable_bytes(data)))
return bytes([x for x in range(1,32,1)])
def restart():
reload(handshakeHandler)

View File

@@ -0,0 +1,53 @@
"""
if client.state == 0: # Perform handshake if possible
client.state = 1
return b"\x1a\x1e\xd1\xd1"
if client.state == 1:
client.state = 2
return b"\x19\x00"
if client.state == 2:
client.state = 3
return b"\xe3\x00\x00", b"\x00\x00\x42\x98\x0c\x11\x33\x45\x30\x42\x33\x33\x35\x32\x37\x34\x46\x39\x31\x43\x39\x39\x00"
if client.state == 3:
client.state = 4
return
if client.state == 4:
client.state = 5
return b"\xe3\x01\x00", b"\x00\x80\x42\x68\x22\xc0\xa8\x02\x87\xfb\xaa\x00\x00\x04\x41\x00\x00"
if client.state <= 7:
client.state += 1
return
if client.state == 8:
client.state = 9
return b"\xe5\x02\x00\x02\x00\x02\x80\x00"
if client.state == 9:
client.state = 10
return b"\x01\x00\x32\x28\x06\xbf\xf9\xca\x0f\x03\x00\x87\x00\x29\x04\x00\x64\x90\x09\xaa\xf9\xca\x0f\xbf\xf9\xca\x0f\x05\x00\x87\x00\x17", b"\x03\x00\x32\x28\x06\xc5\xf9\xca\x0f"
if client.state == 10:
client.state = 11
return b"\x03\x80\x32\x48\x09\xb0\xf9\xca\x0f\xd1\xf9\xca\x0f"
if client.state == 11:
client.state = 12
return b"\xe3\x07\x00"
if client.state == 12:
client.state = 13
return b"\x04\x00\x48\x80\x00\x11\xe0\x14\x3c\xe2\x0e\x2f\x9c\xa0\xf0\x09\x00\x91\x00\x80\x0f\xc1\x00\x14\x8b\xcb\x61\x00\x00\x80\x38\x8d\x00\x00\x8c\x42\x40\x20\x00\x00\x00\x00\x10\x10\x00\x00\x00\xc0\x16\xf1\x20\x33\xc0\x00\x00\x00\x00\xa0\x00\x00\x00\xa0\x00\x00\x00\xa0\x00\x00\x00\x08\x00\x00\x00\x04\x00\x00\x00\x05\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
if client.state == 13:
client.state = 14
return
if client.state == 14:
client.state = 15
return b"\xe3\x0a\x00", b"\x05\x00\x48\x80\x80\x13\xe0\x14\x0c\xe6\x01\xbc\xcf\x44\x40\x8e\x5a\x74\x45\xdf\xe7\x54\x10\x0b\x00\x91\x01\x80\x27\xc0\x14\x9d\xe6\x01\xbc\xcf\x44\x40\x8e\x5a\x74\x45\xdf\xe7\x54\x10\x0c\x00\x91\x02\x00\x28\x40\x14\x9e\xe6\x81\xbc\xcf\x44\x40\x8e\x5a\x74\x45\xdf\xe7\x54\x10\x20\x0d\x00\x82\x6c\x04\x14\x80\xcf\x00\x20\x3f\xe0\x00\x00\x00\x00\x03\x79\x9e\x88\x81\x1c\xb4\xe8\x8b\xbf\xce\xa8\x27\xb2\x50\xc8\x60\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
if client.state == 15:
client.state = 16
return
if client.state == 16:
client.state = 17
return b"\xe3\x0b\x00", b"\x07\x00\x42\x20\x14\x81\xf8\x01"
if client.state == 17:
client.state = 18
return
if client.state == 18:
#client.state = 19
return b"\xe3\x0c\x00", b"\x07\x80\x38\x40\x00\x11\x40\xd0\x00\x00\x00\x00"
"""

View File

@@ -0,0 +1,11 @@
import logging
import struct
from ..network.enums import types as TYPE
from .. import glob
logger = logging.getLogger(__name__)
def handle(client: "Client", data: bytes) -> bytes:
return b""

View File

@@ -0,0 +1,120 @@
import struct
import logging
from .. import glob
from ..helpers import dataHelper
logger = logging.getLogger(__name__)
logger.propagate = False # Disable console logging
def handle(client, data):
ping_ip = ".".join((str(x) for x in struct.unpack(b"<4B", data[4:8])))
ping_port = struct.unpack(b"<H", data[9:11])[0]
logger.debug("[%s] -> Pinging to %s:%d" % (client.addr, ping_ip, ping_port))
if len(data) == 11: # We should add our data ontop
if data[-1:] in RESPONSE:
data += RESPONSE[data[-1:]]()
logger.debug("[%s] <- %s" % (client.addr, data))
return data
def query_i():
len_hostname = len(glob.config["server"]["hostname"])
len_mode = len(glob.config["server"]["mode"])
len_language = len(glob.config["server"]["language"])
packet = struct.pack(b"<?HHI%dsI%dsI%ds" % (len_hostname, len_mode, len_language),
False,
len(get_online_players()),
glob.config["server"]["max_players"],
len_hostname,
glob.config["server"]["hostname"].encode(),
len_mode,
glob.config["server"]["mode"].encode(),
len_language,
glob.config["server"]["language"].encode())
return packet
def query_r():
packet_data = []
rules = get_rules()
packet_data.append(len(rules)) # len_rules
for name, value in rules.items():
packet_data.append([
len(name), name,
len(value), value
])
flat_packet_data = dataHelper.flatten(packet_data)
packet = struct.pack(b"<H" + b"B%dsB%ds" * flat_packet_data[0] # using len_rules
% tuple(len(y) for x in rules.items() for y in x), # tuple of only rules entries
*flat_packet_data)
return packet
def query_c():
packet_data = []
players_scores = get_players_scores()
packet_data.append(len(players_scores))
for name, value in players_scores.items():
packet_data.append([
len(name), name,
value
])
flat_packet_data = dataHelper.flatten(packet_data)
packet = struct.pack(b"<H" + b"B%dsI" * flat_packet_data[0]
% tuple(len(x) for x in players_scores.keys()),
*flat_packet_data)
return packet
def query_d():
packet_data = []
players = get_online_players()
packet_data.append(len(players))
for player in players:
packet_data.append([
player["id"],
len(player["nick"]), player["nick"],
player["score"],
player["ping"]
])
flat_packet_data = dataHelper.flatten(packet_data)
packet = struct.pack(b"<H" + b"cc%dsII" * flat_packet_data[0]
% [len(player["nick"]) for player in players],
*flat_packet_data)
return packet
def get_online_players(): #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
]
def get_rules(): #TODO
return {b"Rule name sample": b"Rule value", b"weburl": b"https://git.osufx.com/Sunpy/sampy"}
def get_players_scores(): #TODO
return {b"Sunpy": 64, b"username": 123}
RESPONSE = { #TODO?: p (https://wiki.sa-mp.com/wiki/Query_Mechanism) (We dont really need to do the last handler as it happens somewhat automatically)
b"i": query_i,
b"r": query_r,
b"c": query_c,
b"d": query_d
}

View File

@@ -0,0 +1,16 @@
import struct
index = 0
def handle(client: "Client", data: bytes) -> bytes:
global index
data = LOOKUP[index]
client.send_immediate(data[:3])
index = (index + 1) % 2
return data[3:]
LOOKUP = [
bytes([int(x,16) for x in "e3 00 00 00 00 42 98 0c 11 33 30 45 39 33 39 33 33 36 39 42 35 36 38 43 32 00".split(" ")]),
bytes([int(x,16) for x in "e3 01 00 00 80 42 68 22 c0 a8 02 85 dc f8 01 00 e0 57 00 00".split(" ")])
]

View File

@@ -0,0 +1,58 @@
import struct
from random import randint
from ..helpers.checksumHelper import CheckSum
from ..network.enums import types as TYPE
TEA_ROUNDS = 32
TEA_XOR_MASK = 0x5E94A3CF
KEY = []
def EncryptBlock(v0, v1):
sum = 0
for i in range(TEA_ROUNDS):
v0 += ((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + key[sum & 3])
def Encrypt(var_input): # bytes
var_input_len = len(var_input) + TYPE.SIZEOF_CHAR * 2 # (checksum, encodedPad)
paddingBytes = 0
if var_input_len % 8 != 0:
paddingBytes = 8 - (var_input_len % 8)
finalLen = var_input_len + paddingBytes
# Randomize pad size for some reason
encodedPad = randint(0, TYPE.MAX_UCHAR)
encodedPad <<= 4
encodedPad &= 0xF0 # Remove bits lost from UCHAR size (C) [BYTE MAGIC]
encodedPad |= paddingBytes
var_output = [0] * finalLen #bytes(finalLen)
var_output[2 + paddingBytes:] = [b for b in var_input]
# Write the pad size variable
var_output[1] = encodedPad
# Write the padding
var_output[2:2+len(paddingBytes)] = bytes([randint(0, TYPE.MAX_UCHAR) for _ in range(len(paddingBytes))])
# Calculate checksum
checksum = CheckSum()
checksum.add(var_output[1:var_input_len + 1 + paddingBytes])
var_checksum = checksum.get()
var_checksum = (var_checksum << 4) ^ var_checksum
var_checksum &= TYPE.MAX_UCHAR # [BYTE MAGIC]
var_output[0] = var_checksum
# Encryption
for i in range(0, finalLen, 8):
v0 = var_output[i]
v1 = var_output[i + TYPE.SIZEOF_INT]
return bytes(var_output)

View File

@@ -0,0 +1,3 @@
from . import dataHelper
from . import TEABlockEncryptor
from . import checksumHelper

View File

@@ -0,0 +1,2 @@
def readable_bytes(byteStream: bytes) -> str:
return " ".join([(hex(b) if b >= 16 else hex(b).replace("x", "x0"))[2:] for b in byteStream])

View File

@@ -0,0 +1,25 @@
from ..network.enums import types as TYPE
# TODO: Single function if Object's state is not needed
class CheckSum:
def __init__(self):
self.clear()
def clear(self):
self.sum = 0
self.r = 55665
self.c1 = 52845
self.c2 = 22719
def add(self, byteArray):
[self._add(b) for b in byteArray]
def _add(self, byte):
cipher = byte ^ (self.r >> 8)
cipher &= TYPE.MAX_UCHAR # [BYTE MAGIC]
self.r = (cipher + self.r) * self.c1 + self.c2
self.r &= TYPE.MAX_USHORT # [BYTE MAGIC]
self.sum += cipher
def get(self):
return self.sum

View File

@@ -0,0 +1,2 @@
def flatten(l):
return flatten(l[0]) + (flatten(l[1:]) if len(l) > 1 else []) if type(l) is list else [l]

View File

@@ -0,0 +1,3 @@
from . import consts
from . import enums
from . import compression

View File

@@ -0,0 +1,50 @@
import logging
from .. import glob
# 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"
logger = logging.getLogger(__name__)
def uncompress(byteStream: bytes) -> bytearray:
"""Uncompress 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 = xor_every_other_byte(get_port_xor_key(), data)
data = run_though_lookup_table(data)
if checksum != calc_checksum(data):
logger.error("Checksum failed!\n\tExpected: %d\n\tGot: %d" % (checksum, calc_checksum(data)))
raise Exception("Checksum failed!")
return data
def xor_every_other_byte(xor, byteStream: bytearray):
# xor: (server_port ^ 0xCCCC) & 0xFF
for i in range(1, len(byteStream), 2):
byteStream[i] = byteStream[i] ^ xor
return byteStream
def run_though_lookup_table(byteStream: bytes):
return bytearray([LOOKUP_TABLE[b] for b in byteStream])
def calc_checksum(byteStream: bytes):
checksum = 0
for i in range(len(byteStream)):
checksum = checksum ^ byteStream[i] & 0xAA
return checksum
# It should be faster to call a function and do an if, then to read the property of a dict and do bit operations after
_port_xor_key = None
def get_port_xor_key():
global _port_xor_key
if _port_xor_key is None:
_port_xor_key = (glob.config["port"] ^ 0xCCCC) & 0xFF
return _port_xor_key

View File

@@ -0,0 +1,2 @@
MTU_SIZE = 576
UDP_HEADER_SIZE = 28

View File

@@ -0,0 +1,4 @@
from . import packetTypeRaknet
from . import packetTypeSamp
from . import packetRPC
from . import types

View File

@@ -0,0 +1,137 @@
# netrpc (client)
RPC_ServerJoin = "xy"
RPC_ServerQuit = "ab"
RPC_InitGame = "ac"
RPC_VehicleSpawn = "am"
RPC_VehicleDestroy = "an"
RPC_SetCheckpoint = "ao"
RPC_DisableCheckpoint = "ap"
RPC_SetRaceCheckpoint = "aq"
RPC_DisableRaceCheckpoint = "ar"
RPC_GameModeRestart = "at"
RPC_ConnectionRejected = "au"
RPC_ClientMessage = "av"
RPC_WorldTime = "aw"
RPC_Pickup = "ax"
RPC_DestroyPickup = "ay"
RPC_DestroyWeaponPickup = "az"
RPC_Weather = "bb"
RPC_Instagib = "bc"
RPC_SetTimeEx = "be"
RPC_ToggleClock = "bf"
# netrpc (both)
RPC_Chat = "ad"
RPC_Privmsg = "ae"
RPC_TeamPrivmsg = "af"
RPC_RequestClass = "ag"
RPC_RequestSpawn = "ah"
RPC_Spawn = "ai"
RPC_Death = "aj"
RPC_EnterVehicle = "ak"
RPC_ExitVehicle = "al"
RPC_UpdateScoresPingsIPs = "as"
RPC_SvrStats = "em"
RPC_ScmEvent = "ba"
# scriptrpc
RPC_ScrSetSpawnInfo = "bg"
RPC_ScrSetPlayerTeam = "bh"
RPC_ScrSetPlayerSkin = "bi"
RPC_ScrSetPlayerName = "bj"
RPC_ScrSetPlayerPos = "bk"
RPC_ScrSetPlayerPosFindZ = "bl"
RPC_ScrSetPlayerHealth = "bm"
RPC_ScrPutPlayerInVehicle = "bn"
RPC_ScrRemovePlayerFromVehicle = "bo"
RPC_ScrSetPlayerColor = "bp"
RPC_ScrDisplayGameText = "bq"
RPC_ScrSetInterior = "br"
RPC_ScrSetCameraPos = "bs"
RPC_ScrSetCameraLookAt = "bt"
RPC_ScrSetVehiclePos = "bu"
RPC_ScrSetVehicleZAngle = "bv"
RPC_ScrVehicleParams = "bw"
RPC_ScrSetCameraBehindPlayer = "bx"
RPC_ScrTogglePlayerControllable = "by"
RPC_ScrPlaySound = "bz"
RPC_ScrSetWorldBounds = "ca"
RPC_ScrHaveSomeMoney = "cb"
RPC_ScrSetPlayerFacingAngle = "cc"
RPC_ScrResetMoney = "cd"
RPC_ScrResetPlayerWeapons = "ce"
RPC_ScrGivePlayerWeapon = "cf"
RPC_ScrRespawnVehicle = "cg"
RPC_ScrLinkVehicle = "ch"
RPC_ScrSetPlayerArmour = "ci"
RPC_ScrDeathMessage = "cj"
RPC_ScrSetMapIcon = "ck"
RPC_ScrDisableMapIcon = "cl"
RPC_ScrSetWeaponAmmo = "cm"
RPC_ScrSetGravity = "cn"
RPC_ScrSetVehicleHealth = "co"
RPC_ScrAttachTrailerToVehicle = "cp"
RPC_ScrDetachTrailerFromVehicle = "cq"
RPC_ScrCreateObject = "cr"
RPC_ScrSetObjectPos = "cs"
RPC_ScrSetObjectRotation = "ct"
RPC_ScrDestroyObject = "cu"
RPC_ScrSetPlayerVirtualWorld = "cv"
RPC_ScrSetVehicleVirtualWorld = "cw"
RPC_ScrCreateExplosion = "cx"
RPC_ScrShowNameTag = "cy"
RPC_ScrMoveObject = "cz"
RPC_ScrStopObject = "da"
RPC_ScrNumberPlate = "db"
RPC_ScrTogglePlayerSpectating = "dc"
RPC_ScrSetPlayerSpectating = "dd"
RPC_ScrPlayerSpectatePlayer = "de"
RPC_ScrPlayerSpectateVehicle = "df"
RPC_ScrRemoveComponent = "dg"
RPC_ScrForceSpawnSelection = "dh"
RPC_ScrAttachObjectToPlayer = "dt"
RPC_ScrInitMenu = "du"
RPC_ScrShowMenu = "dv"
RPC_ScrHideMenu = "dw"
RPC_ScrSetPlayerWantedLevel = "dz"
RPC_ScrShowTextDraw = "ea"
RPC_ScrHideTextDraw = "eb"
RPC_ScrEditTextDraw = "ee"
RPC_ScrAddGangZone = "ef"
RPC_ScrRemoveGangZone = "eg"
RPC_ScrFlashGangZone = "eh"
RPC_ScrStopFlashGangZone = "ei"
RPC_ScrApplyAnimation = "eo"
RPC_ScrClearAnimations = "eq"
RPC_ScrSetSpecialAction = "ep"
RPC_ScrEnableStuntBonus = "ec"
RPC_ScrUsePlayerPedAnims = "a1"
RPC_ScrToggleVehicleMarker = "a4"
RPC_ScrMoveTextdraw = "a5"
RPC_ScrSetPlayerVisibleInScoreBoard = "a6"
# netrpc (server)
RPC_ClientJoin = "xx"
RPC_ServerCommand = "dj"
RPC_SetInteriorId = "dk"
RPC_ClickMap = "dl"
RPC_VehicleDestroyed = "dm"
RPC_PickedUpWeapon = "dn"
RPC_PickedUpPickup = "do"
RPC_MenuSelect = "dx"
RPC_MenuQuit = "dy"
RPC_UnderMapTeleport = "a2"
RPC_ResolutionChanged = "a3"
# rcon
RPC_RconConnect = "dp"
RPC_RconCommand = "dq"
RPC_RconEvent = "dr"
RPC_RconPlayerInfo = "ds"
# anticheat
RPC_ACAuthRequest = "ej"
RPC_ACAuthResponse = "ek"
RPC_ACAuthEngineLoaded = "el"
RPC_ACServerProtected = "bd"

View File

@@ -0,0 +1,113 @@
INTERNAL_PING = 0x00
PING = 0x01
PING_OPEN_CONNECTIONS = 0x02
CONNECTED_PONG = 0x03
REQUEST_STATIC_DATA = 0x04
CONNECTION_REQUEST = 0x05
SECURED_CONNECTION_RESPONSE = 0x06
SECURED_CONNECTION_CONFIRMATION = 0x07
RPC_MAPPING = 0x08
DETECT_LOST_CONNECTIONS = 0x09
OPEN_CONNECTION_REQUEST = 0x0a
OPEN_CONNECTION_REPLY = 0x0b
RPC = 0x0c
RPC_REPLY = 0x0d
BROADCAST_PINGS = 0x0e
SET_RANDOM_NUMBER_SEED = 0x0f
CONNECTION_REQUEST_ACCEPTED = 0x10
CONNECTION_ATTEMPT_FAILED = 0x11
NEW_INCOMING_CONNECTION = 0x18
NO_FREE_INCOMING_CONNECTIONS = 0x13
DISCONNECTION_NOTIFICATION = 0x14
CONNECTION_LOST = 0x15
RSA_PUBLIC_KEY_MISMATCH = 0x16
CONNECTION_BANNED = 0x17
INVALID_PASSWORD = 0x18
MODIFIED_PACKET = 0x19
TIMESTAMP = 0x1a
PONG = 0x1b
RECEIVED_STATIC_DATA = 0x1c
REMOTE_DISCONNECTION_NOTIFICATION = 0x1d
REMOTE_CONNECTION_LOST = 0x1e
REMOTE_NEW_INCOMING_CONNECTION = 0x1f
REMOTE_EXISTING_CONNECTION = 0x20
REMOTE_STATIC_DATA = 0x21
FILE_LIST_TRANSFER_HEADER = 0x22
FILE_LIST_TRANSFER_FILE = 0x23
DDT_DOWNLOAD_REQUEST = 0x24
QUERY_MASTER_SERVER = 0x25
MASTER_SERVER_DELIST_SERVER = 0x26
MASTER_SERVER_UPDATE_SERVER = 0x27
MASTER_SERVER_SET_SERVER = 0x28
RELAYED_CONNECTION_NOTIFICATION = 0x29
ADVERTISE_SYSTEM = 0x2a
TRANSPORT_STRING = 0x2b
REPLICA_MANAGER_CONSTRUCTION = 0x2c
REPLICA_MANAGER_DESTRUCTION = 0x2d
REPLICA_MANAGER_SCOPE_CHANGE = 0x2e
REPLICA_MANAGER_SERIALIZE = 0x2f
REPLICA_MANAGER_DOWNLOAD_COMPLETE = 0x30
CONNECTION_GRAPH_REQUEST = 0x31
CONNECTION_GRAPH_REPLY = 0x32
CONNECTION_GRAPH_UPDATE = 0x33
CONNECTION_GRAPH_NEW_CONNECTION = 0x34
CONNECTION_GRAPH_CONNECTION_LOST = 0x35
CONNECTION_GRAPH_DISCONNECTION_NOTIFICATION = 0x36
ROUTE_AND_MULTICAST = 0x37
RAKVOICE_OPEN_CHANNEL_REQUEST = 0x38
RAKVOICE_OPEN_CHANNEL_REPLY = 0x39
RAKVOICE_CLOSE_CHANNEL = 0x3a
RAKVOICE_DATA = 0x3b
AUTOPATCHER_GET_CHANGELIST_SINCE_DATE = 0x3c
AUTOPATCHER_CREATION_LIST = 0x3d
AUTOPATCHER_DELETION_LIST = 0x3e
AUTOPATCHER_GET_PATCH = 0x3f
AUTOPATCHER_PATCH_LIST = 0x40
AUTOPATCHER_REPOSITORY_FATAL_ERROR = 0x41
AUTOPATCHER_FINISHED = 0x42
AUTOPATCHER_RESTART_APPLICATION = 0x43
NAT_PUNCHTHROUGH_REQUEST = 0x44
NAT_TARGET_NOT_CONNECTED = 0x45
NAT_TARGET_CONNECTION_LOST = 0x46
NAT_CONNECT_AT_TIME = 0x47
NAT_SEND_OFFLINE_MESSAGE_AT_TIME = 0x48
DATABASE_QUERY_REQUEST = 0x49
DATABASE_UPDATE_ROW = 0x4a
DATABASE_REMOVE_ROW = 0x4b
DATABASE_QUERY_REPLY = 0x4c
DATABASE_UNKNOWN_TABLE = 0x4d
DATABASE_INCORRECT_PASSWORD = 0x4e

View File

View File

@@ -0,0 +1,20 @@
SIZEOF_CHAR = 1
SIZEOF_SHORT = 2
SIZEOF_INT = 4
SIZEOF_LONG = 4
SIZEOF_LONG_LONG = 8
SIZEOF_FLOAT = 4
SIZEOF_DOUBLE = 8
MAX_CHAR = 0x7F
MAX_UCHAR = 0xFF
MAX_SHORT = 0x7FFF
MAX_USHORT = 0xFFFF
MAX_INT = 0x7FFFFFFF
MAX_UINT = 0xFFFFFFFF # 1 << SIZEOF_INT * 8 - 1 (I had a dream of doing it this way.. but Robin said no >.>)
MAX_LONG = 0x7FFFFFFF
MAX_ULONG = 0xFFFFFFFF
MAX_LONG_LONG = 0x7FFFFFFFFFFFFFFF
MAX_ULONG_LONG = 0xFFFFFFFFFFFFFFFF

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

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

66
sampy/raknet/bitStream.py Normal file
View File

@@ -0,0 +1,66 @@
class BitStream:
def __init__(self, data: list = []):
self.bits_used = 0
self.read_offset = 0
self.data = data
def write(self, c_bytes): # ?bytes_to_write
bytes_to_write = len(c_bytes)
if not len(c_bytes):
return
self.write_bits(c_bytes, bytes_to_write << 3, True)
def read(self, bytes_to_read: int):
bits_to_read = bytes_to_read << 3
if self.read_offset & 7 == 0:
if self.read_offset + bits_to_read > self.bits_used:
return False
read_offset_bytes = self.read_offset >> 3
data = self.data[read_offset_bytes:read_offset_bytes + bytes_to_read]
self.read_offset += bits_to_read
return data
return self.read_bits(bits_to_read)
def read_bits(self, bits_to_read: int, align_right: bool = False):
if bits_to_read <= 0:
return False
if self.read_offset + bits_to_read > self.bits_used:
return False
offset = 0
bits_read_offset_mod8 = self.read_offset & 7
def write_bits(self, c_bytes, bits_to_write: int, align_right: bool = False):
if not bits_to_write:
return
offset = 0
bits_used_mod8 = self.bits_used & 7
while bits_to_write:
byte = c_bytes[offset]
if bits_to_write < 8 and align_right:
byte <<= 8 - bits_to_write
if bits_used_mod8 == 0:
self.data[self.bits_used >> 3] = byte
else:
self.data[self.bits_used >> 3] |= byte >> bits_used_mod8
if 8 - bits_used_mod8 < 8 and 8 - bits_used_mod8 < bits_to_write:
self.data[(self.bits_used >> 3) + 1] = byte << (8 - bits_used_mod8)
if bits_to_write >= 8:
self.bits_used += 8
else:
self.bits_used += bits_to_write
bits_to_write -= 8
offset += 1

62
sampy/server.py Normal file
View File

@@ -0,0 +1,62 @@
import socket
import select
import logging
from threading import Thread
from .client import Client
logger = logging.getLogger(__name__)
class Server(Thread):
BUFF_SIZE = 65535
def __init__(self, host, port):
super(Server, self).__init__()
self.daemon = True
self.host = host
self.port = port
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.host, self.port))
self.socket.setblocking(0)
self.clients = {}
def run(self):
# Main server loop
while True:
(incomming, _, _) = select.select(
[self.socket],
[],
[], 1)
if incomming:
data, addr = self.socket.recvfrom(self.BUFF_SIZE)
if addr in self.clients:
self.clients[addr].handle_data(data)
else:
try:
self.clients[addr] = Client(self, addr)
logger.info("Accepted connection from %s:%s" % (addr[0], addr[1]))
self.clients[addr].handle_data(data)
except socket.error:
try:
self.socket.close()
except:
pass
logger.warn("Something went very wrong...")
#exit()
return
clients = [x for x in self.clients.values()
if x.get_buffer_size() > 0]
for client in clients:
client.write_socket()
"""for c in self.clients.values():
print(c.__writebuffer)"""
def restart(self):
[c.restart() for c in self.clients]