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 withDapiMaster- Master server. All other servers/client connecting toMasterBox- client/server withBotsBot- bot connected/spawned byBox.Customer- user registered on website.CustomerhaveCustomerID(integer) and -CustomerKEY(string 32 characters) to authCustomerSubscription- what customer boughtTask- task for cluster. Like "feed customer123with 20 bots in party serverABC123, he have10minutes left". There isTaskIDExtension- browser extension thatCustomerinstalls in browser.ExtensionknowsCustomerIDandCustomerKEYReception- WebSocket server that waits connections fromCustomer'sExtensionWapi- part ofMasterwith HTTP API (RESTful API)Wapic-WapiclientDapi- part ofWebsitewith 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 noauthfield foundAUTH_FAILED- Invalid passwordEMPTY_COMMAND- There is nocmdfield foundUNKNOWN_COMMAND- Unknowncmdfield value
count_boxes- counts connectedBoxescountlist_boxes- list for IDs ofBoxeslist- array of IDsping_box(box_id)- calculates average ping ofBoxaverage- 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)Receptionswants to authorizeCustomerusername- username ofCustomersubscriptions- array of subscriptions. Empty array if noneid- numbertype-ffaorpartycount- number of availableBotstime- number of remaining secondsactivated-true/falseis 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-ffaorpartycount- number of availableBotsremain- number of remaining secondsactivated-true/falseis 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)- getcountof 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 toreceptioninitialized- initialized byreceptionhooked- is hooked to agar.io connectionkilled- killed byreceptionand no more connections will be madereconnect_attempt- reconnect attempt numberfeedback_queue- queue of errorscustomer_id-customeridcustomer_key-customerkeysocket- EIO socketstate- checkExtension.stateengaged- array of engaged IDs on this sessiontarget- mouse / cords / nickname / ball
engage_subscription(id)- engage. Will returnfalseif 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 toreceptionserveron.disconnect()- disconnected fromreceptionserveron.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.usernameusernameobj.subscriptionsnumber of subscriptions counton.notice(notice_code, kill)- notice received.Codeslisted below.killtrue/false - close connection without reconnect or noton.subscriptionAdded(sub)sub.idnumbersub.countcount of botssub.remainseconds remainingsub.expireexpire timestamp. Ignore for unactivatedsub.typetypepartyorffasub.activatedis activatedtrue/falseon.versionUpgrade(version)- upgrade version of script (load script as 'script.js?NEW_VERSION')versionnumber of new versionon.authorizedcustomer 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
0Client->Serverauth(version, customer_id, customer_key)1Client->Serverfeedback(msg)2Server->Clientuserinfo(object {username: '', subscriptions: [ check Dapi commands ]})3Server->Clientnotice(notice_code, kill)4Server->Clientupgrade_version(new_version)5Client->Serverengage_subscription(id, region, gamemode, key)engage7Server->Clientengaged(id, this_session)when engaged8Server->Clientdisengaged(id)when disengaged9Server->Clientconnected(id, count)connected bots amount update10Server->Clientactivated(id)subscription activated and expire timer started11Client->Serverleaders(arr)send new leader board12Client->Servernew_cords(x, y)send new position to target13Client->Servernew_ball(ball_id)send new ball_id to target14Client->Servernew_nickname(nick)send new nickname target
0Version mismatch, tell user to refresh page (you can ignore this since we haveupgrade_version(new_version)packet)1Error while communicating withDapiserver. Tell user to try again later2Auth failed. CustomerID or CustomerKEY is incorrect.3Subscription not found4Subscription expired5Subscription type mismatch (for example user on party, but subscription for ffa)6Subscription did not received region code. Try to refresh page7Subscription did not received party key. Try to refresh page8Subscription is already in use. Check opened tabs or who you gave you password9Cluster is overloaded and unable to engage subscription. Tell user that free time will be added when cluster will be fixed.10Subscription did not received server. Try to refresh page11Subscription received wrong format of FFA key. Try to refresh page12Subscription received wrong format of party key. Try to refresh page13Subscription received wrong format server address. Try to refresh page14Task can't connect to party server. Is it closed?15Subscription time is expired while it was engaged16Failed to activate subscription on DAPI17Box 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
?versionto .js file to bypass cache.versionwill be stored SOMEWHERE(?) in extension memory. Receptioncheck 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