In 2016, I developed a fully functional subscription-based bot service for the popular game agar.io. The project involved creating a bot cluster from scratch using JavaScript and Node.js, utilizing the concept of classes and prototypes. The bot cluster allowed users to control hundreds of cells (bots) within the game, giving them a tremendous advantage against other enemy players. The bot cluster was hosted on multiple Ubuntu servers, while a browser extension was developed as the graphical user interface (GUI) to enable users to interact with the bots.
By installing a chrome extension and authenticating with a valid subscription key, users could spawn hundreds of cells in game. By default, these cells were programmed to target the user's cell coordinates (x,y pos), enabling the user to consume them and grow larger. Additionally, users had the option to control the cells' movement direction by moving their mouse cursor, initiating gameplay where the cells would consume each other to grow larger and eat other enemy cells. The main objective was to become the largest cell by strategically maneuvering the bots and consuming others while avoiding being consumed by larger enemy cells.
The development of this project was solely driven by educational purposes and a desire to gain a deeper understanding of websockets, binary protocols, and reverse engineering. The project aimed to acquire practical knowledge while delivering an engaging and fun gameplay experience.
Website
- website withDapi
Master
- Master server. All other servers/client connecting toMaster
Box
- client/server withBots
Bot
- bot connected/spawned byBox
.Customer
- user registered on website.Customer
haveCustomerID
(integer) and -CustomerKEY
(string 32 characters) to authCustomer
Subscription
- what customer boughtTask
- task for cluster. Like "feed customer123
with 20 bots in party serverABC123
, he have10
minutes left". There isTaskID
Extension
- browser extension thatCustomer
installs in browser.Extension
knowsCustomerID
andCustomerKEY
Reception
- WebSocket server that waits connections fromCustomer's
Extension
Wapi
- part ofMaster
with HTTP API (RESTful API)Wapic
-Wapi
clientDapi
- part ofWebsite
with HTTP API (RESTful API)
Master
- Master
server, IP must be hidden from end users. All other servers/clients connects to Master
.
Box
- client with Bots
. Box
connecting to Master
and listens from commands. Should be launched on multiple VPSes
Reception
- websocket server that listens for connections from Extensions
. Its IP is public accessible and should be on same IP as website.
Part of Master
with HTTP API (RESTful API)
http://127.0.0.1:13500/?auth=WAPI_PASSWORD_HERE&cmd=count_boxes http://127.0.0.1:13500/?auth=WAPI_PASSWORD_HERE&cmd=ping_box&id=BOX_ID_HERE
JSON Format: Example:
{'status': 'success', 'a': 'b'}
{'status': 'error', 'code': 'UNSUPPORTED_METHOD', 'reason': 'Unknown method: DELETE'}
Always there status
with success
or error
. If error
then there is code
and reason
. Never show any of this to end user!
UNSUPPORTED_METHOD
- Unknown methodURL_PARSE_FAIL
- Unable to parse URLEMPTY_QUERY
- Unable to get query from URLAUTH_REQUIRED
- There is noauth
field foundAUTH_FAILED
- Invalid passwordEMPTY_COMMAND
- There is nocmd
field foundUNKNOWN_COMMAND
- Unknowncmd
field value
count_boxes
- counts connectedBoxes
count
list_boxes
- list for IDs ofBoxes
list
- array of IDsping_box(box_id)
- calculates average ping ofBox
average
- numberhistory
- array of numbers of last pings- BOX_NOT_FOUND
Part of Website
with HTTP API (RESTful API)
Same as WAPI
tick
- decrease time (default 60 sec) from all activated subscriptions.- No feedback needed
authorize_customer(id, key)
Receptions
wants to authorizeCustomer
username
- username ofCustomer
subscriptions
- array of subscriptions. Empty array if noneid
- numbertype
-ffa
orparty
count
- number of availableBots
time
- number of remaining secondsactivated
-true
/false
is it activated or this is first time usage
- INVALID_CUSTOMER - return this error if there is no such customer
subscription_info(id)
- request subscription infoowner
- ID of customertype
-ffa
orparty
count
- number of availableBots
remain
- number of remaining secondsactivated
-true
/false
is it activated or this is first time usage- INVALID_SUBSCRIPTION - return error if subscription not found
cluster_overloaded(subscription_id, customer_id)
- we need more servers. After solve, give this subscription some free time- No feedback needed
request_proxies(subscription_id, region, count)
- getcount
of sockslist
- array of arrays of socks like[ip, port, version]
like[['1.2.3.4','8888','5'],['2.2.2.2','123','4']]
subscription_activate(id)
- activate subscription since this is first use- No feedback needed
var econ = new Extension();
connected
- is connected toreception
initialized
- initialized byreception
hooked
- is hooked to agar.io connectionkilled
- killed byreception
and no more connections will be madereconnect_attempt
- reconnect attempt numberfeedback_queue
- queue of errorscustomer_id
-customer
idcustomer_key
-customer
keysocket
- EIO socketstate
- checkExtension.state
engaged
- array of engaged IDs on this sessiontarget
- mouse / cords / nickname / ball
engage_subscription(id)
- engage. Will returnfalse
if hook is not installed, this mean extension was loaded after agario scripts connected to server. Fix it or ask user to connect to new server so hook can be installed on new connection.
Using EventEmitter
on.connect()
- connected toreception
serveron.disconnect()
- disconnected fromreception
serveron.reconnect(attempt, delay)
- reconnecting, first attempt will be 0 with 0 delay, do not show iton.cleanup()
- extension is cleaned itself after disconnecton.userinfo(obj)
- userinfo received,obj.username
usernameobj.subscriptions
number of subscriptions counton.notice(notice_code, kill)
- notice received.Codes
listed below.kill
true/false - close connection without reconnect or noton.subscriptionAdded(sub)
sub.id
numbersub.count
count of botssub.remain
seconds remainingsub.expire
expire timestamp. Ignore for unactivatedsub.type
typeparty
orffa
sub.activated
is activatedtrue
/false
on.versionUpgrade(version)
- upgrade version of script (load script as 'script.js?NEW_VERSION')version
number of new versionon.authorized
customer is authorized and readyon.subscriptionEngaged(id, opt)
subscription engagedid
- ID of subscriptionopt.this_session
- engaged in this session or not (opened in another tab/browser/computer)on.subscriptionDisengaged(id)
subscription disengagedid
- ID of subscriptionon.subscriptionConnected(id, count)
subscription connected bots countid
- ID of subscriptioncount
- amount of connected botson.subscriptionActivated(id)
subscription activated and expire timer startedid
- ID of subscription
Props:
target
- current target typemy_ball
- last my ball ID
Events:
on.error(msg)
- erroron.mousePos(x, y)
- new mouse position caughton.myNewBall(ball_id)
- our new ball idon.leaderBoardUpdate(arr)
- array of leaders IDson.hook(server, key)
- hooked to connection
0
Client->Serverauth(version, customer_id, customer_key)
1
Client->Serverfeedback(msg)
2
Server->Clientuserinfo(object {username: '', subscriptions: [ check Dapi commands ]})
3
Server->Clientnotice(notice_code, kill)
4
Server->Clientupgrade_version(new_version)
5
Client->Serverengage_subscription(id, region, gamemode, key)
engage7
Server->Clientengaged(id, this_session)
when engaged8
Server->Clientdisengaged(id)
when disengaged9
Server->Clientconnected(id, count)
connected bots amount update10
Server->Clientactivated(id)
subscription activated and expire timer started11
Client->Serverleaders(arr)
send new leader board12
Client->Servernew_cords(x, y)
send new position to target13
Client->Servernew_ball(ball_id)
send new ball_id to target14
Client->Servernew_nickname(nick)
send new nickname target
0
Version mismatch, tell user to refresh page (you can ignore this since we haveupgrade_version(new_version)
packet)1
Error while communicating withDapi
server. Tell user to try again later2
Auth failed. CustomerID or CustomerKEY is incorrect.3
Subscription not found4
Subscription expired5
Subscription type mismatch (for example user on party, but subscription for ffa)6
Subscription did not received region code. Try to refresh page7
Subscription did not received party key. Try to refresh page8
Subscription is already in use. Check opened tabs or who you gave you password9
Cluster is overloaded and unable to engage subscription. Tell user that free time will be added when cluster will be fixed.10
Subscription did not received server. Try to refresh page11
Subscription received wrong format of FFA key. Try to refresh page12
Subscription received wrong format of party key. Try to refresh page13
Subscription received wrong format server address. Try to refresh page14
Task can't connect to party server. Is it closed?15
Subscription time is expired while it was engaged16
Failed to activate subscription on DAPI17
Box crashed while task was engaged
Not to forget:
- If customer engages bots and then refreshes page and then engages again immediately give him timeout to protect from overload cluster
- Website should write to extension memory
customer_id
,customer_key
- Extension should write its version SOMEWHERE(?) into memory
- Injector should add
?version
to .js file to bypass cache.version
will be stored SOMEWHERE(?) in extension memory. Reception
check ws.state of user before send- If cluster don't have free servers on subscription engage, then notice user and add subscription time
TODO website:
Plans should have "count" of bots, "time" remained seconds to use, "active" is it activated by master after first use
Customers should have CustomerID
, CustomerKEY
(randomly generated secret string)
When customer buys plan do not activate it immediately, set active=false
- Send invalid IP for FFA server
- Send invalid leaders list for FFA servers
- Send fake party server with millions of balls in it
- Send gigabytes of data to reception