So you've decided to create your own server for a Supercell game and Python caught your eye. Among countless questionable projects, you stumbled upon PySupercell Core. What now?
Why PySupercell Core?
Supercell games (Clash of Clans, Brawl Stars etc.) share similar architecture—they’re built on a core (server foundation). PySupercell Core (PSC) is a fresh Python core that:
- Implements Supercell’s base server architecture
- Easily adapts to any SC game
- But isn’t a ready-made server—you’ll write the logic yourself
Most other Python servers/cores are slow and outdated. PSC is fast and user-friendly
Installing PSC
-
Clone the repo:
git clone https://github.com/REtard-1337/pysupercell-core cd pysupercell-core
-
Install dependencies:
pip install -r requirements.txt
Picking Ur Game
Navigate to logic/messages/
and find logic_magic_message_factory.py
Here magic
is Clash of Clans' codename. Swap it for your game:
Game | Codename |
---|---|
Clash of Clans | magic |
Brawl Stars | laser |
Clash Royale | scroll |
Hay Day | soil |
Boom Beach | reef |
Squad Busters | squad |
Server Configuration
Open config.toml
and set game parameters. Example for Brawl Stars v52.13.77:
[game]
major_version = 52
build = 13
content_version = 77
environment = "int"
Now your server seems ready... but when you launch it...
— Wait, why is the client stuck at "Connecting to server..."? That means PSC is broken!!!
— Nope, it works! PSC is a core, not a full server. You must implement all packets yourself
Writing Messages
Let’s take LoginMessage
as an example.
- Create
logic/messages/auth/login_message.py
-
Code the packet:
from titan.message.piranha_message import PiranhaMessage from titan.math.logic_long import LogicLong class LoginMessage(PiranhaMessage): def __init__(self): super().__init__() self.account_id = LogicLong() self.pass_token = "" self.major_version = 0 def decode(self): super().decode() self.account_id = self.stream.read_long() self.pass_token = self.stream.read_string() self.major_version = self.stream.read_int() # ... def get_message_type(self) -> int: return 10101
But this seems complicated, so let’s break it down.
First, imports:
from titan.message.piranha_message import PiranhaMessage
from titan.math.logic_long import LogicLong
Then create LoginMessage
class—it must inherit from PiranhaMessage
:
class LoginMessage(PiranhaMessage):
...
PiranhaMessage
is the base class for all packets. It providesstream
(like Classic-Brawl’s Reader/Writer)
Initialize fields in the constructor:
def __init__(self):
super().__init__()
self.account_id = LogicLong()
self.pass_token = ""
self.major_version = 0
Now the decode:
def decode(self):
super().decode()
self.account_id = self.stream.read_long()
self.pass_token = self.stream.read_string()
self.major_version = self.stream.read_int()
And message type:
def get_message_type(self) -> int:
return 10101
Fascinating! But what about server responses?
Create login_ok_message.py
next to login_message.py
:
from titan.math.logic_long import LogicLong
from titan.message.piranha_message import PiranhaMessage
class LoginOkMessage(PiranhaMessage):
def __init__(self) -> None:
super().__init__()
self.account_id = LogicLong()
self.home_id = LogicLong()
self.pass_token = ""
...
def encode(self):
super().encode()
self.stream.write_long(self.account_id)
self.stream.write_long(self.home_id)
self.stream.write_string(self.pass_token)
# ...
def get_message_type(self) -> int:
return 20104
Not much to explain—since this is a server packet, we implement encode()
using fields from the constructor
Handling Packets
Notice no process()
, execute()
, or handle()
in message classes? Supercell uses a different approach—all packets are handled via MessageManager.receive_message
Example for LoginMessage
:
# server/protocol/message_manager.py
from logic.messages.auth import LoginMessage, LoginOkMessage
from titan.math import LogicLong
class MessageManager:
def __init__(self, connection):
self.connection = connection
async def receive_message(self, message):
match message.get_message_type():
case 10101:
await self.on_login(message)
case _:
... # other stuff
async def on_login(self, message: LoginMessage):
response = LoginOkMessage()
response.account_id = ...
response.pass_token = ...
# ...
await self.connection.send_message(response)
What’s happening?
First, message_manager.py
isn’t empty—it has base structure (see screenshot below)
We added a case to handle login_message
when its ID arrives:
async def receive_message(self, message):
...
case 10101:
await self.on_login(message) # await 'cause async
Now observe on_login
:
async def on_login(self, message: LoginMessage):
response = LoginOkMessage()
response.account_id = ...
response.pass_token = ...
# ...
await self.connection.send_message(response)
We pass the incoming packet:
async def on_login(self, message: LoginMessage):
Create a response packet:
response = LoginOkMessage()
Fill its fields:
response.account_id = ...
response.pass_token = ...
Then send it back:
await self.connection.send_message(response)
Got questions? DM:
- t.me/TheBladewise1337
- t.me/user_with_username
Top comments (0)