RustMS is an attempt at implementing the Maplestory server end from scratch in Rust.
It's very work-in-progress and really only barely off the ground at this point, but it's been a fun evening/weekend side project so far!
This project was abandoned a few months after I initially started it, but I've revisited it now (2026/03) after feeling an itch to vibe code/explore Codex while I'm on vacation and away from work-work. The initial purpose of this project was to learn Rust, but it doesn't look like I'm doing a whole lot of that in this revisit; instead, I'm exploring how I can vibe code more sustainably (and also get a feel for ecosystems outside of Claude Code).
The first thing that came to me is that I need to build some test-rails to guide implementation, hence I "built" an integration harness and also moved the entire HeavenClient project into the repository so that I can do manual verification as well.
The motivation behind this project is two-fold:
When I started this project, I knew next to no Rust, however I was quite interested in learning it due to a general interest that I've always had in lower level languages. I've previously learned a bit of C, however I've only really used it in an academic setting (as well as to make this relatively bland shell I made for fun). I thought Rust sounded interesting so I decided to start a fun project as a means of learning it. I'd also been trying to learn Vim for a while and thought caging myself in with it for this project (initially via the VS Code plugin and later neovim alone) would be fun.
The second motivation behind this project comes from the fact that Maplestory has a special place in my heart as the game that probably defined my childhood. I knew people had written and ran their own servers before but for the longest time it hadn't hit me that a lot of these servers had their source up on Github. Having looked at a few servers such as HeavenMS and Valhalla, I realized that I could probably try my hand at writing my own server too and that I could probably have quite a bit of fun with it.
The crypt crate provides the means for encrypting and decrypting packets using
Maplestory's custom AES algorithm as well as its secondary encryption algorithm
that's applied prior to AES. In particular, this library defines the MapleAES
object that is used to encrypt/decrypt and keep track of the encryption nonce for
a unidirectional stream of communication between the client and server. There is
also a module here that provides an interface for encrypting and decrypting
account passwords with bcrypt.
The packet crate provides a simple wrapper for our network packets, as well as an
extension of the Read and Write IO traits that allow us to read and write various
data types to our packets, including little endian integers of different sizes and
length-headered strings.
The game crate holds the domain model: a logged-in Player with its stats,
avatar look, inventory and equipment, skills, keymap, and quest log. It knows
nothing about databases or packets.
The db crate persists that model in PostgreSQL through Diesel and a
connection pool. db::player loads and saves whole players; migrations are
embedded and applied by both servers at startup.
The game-data crate reads static data (maps, portals, NPCs, quests) from the
v83 NX files in assets/game-data (see the README there).
The net crate holds the protocol: opcodes, an opcode registry that routes
each packet to a login or world handler, the handlers themselves, packet
builders, and shared encoders (net::codec) for the structures that recur
across packets (character stats, avatar looks, items, inventories).
The runtime crate runs the servers as Tokio actors. The rust-ms crate has
two binaries: login (port 8484) and world, which hosts every channel in
one process with one port per channel (8485, 8486, 8487 by default). See
agent_docs/server-architecture.md for how they fit together.
RustMS has a login server and a multi-channel world server.
Once a new client initiates a connection, the server responds with a handshake to exchange AES initialization vectors as well as some metadata about the server such as the client version that it expects. Any subsequent communication between the client and the server is encrypted with Maplestory's custom encryption (for lack of a better name) followed by Maplestory's custom AES encryption. Send and Receive data is encrypted using separate nonces (i.e. the initialization vectors and their subsequent mutations) that are both kept in sync between the client and server.
Currently, one can create an account by attempting to log into an account that does not yet exist. The provided password is encrypted and the user/encrypted pw combination is saved in the database. The first login will prompt the user to accept a TOS as well as select their gender before forwarding them to the world select... these two features are currently hardcoded to trigger however in the future they'll be controlled by a configuration property.
Existing accounts can be logged into as expected, and a successful login will forward you to the world select screen as expected.
Currently, the account PIN prompt is bypassed and PINs are not yet supported. In the future we'll implement this too (likely whenever we establish our architecture for handling multiple servers and moving sessions between them... this will likely require changes in how the client session is modeled, which will have a direct impact on pin logins). We'll be putting this behind a configuration property as well.
Currently, one may select a world and one of its channels once logged in. There is one world with three channels, each served on its own port, and players can change channels in game.
Currently, character creation is supported to an extent. One may create persistent, account linked characters that may be viewed on the character selection screen. These characters keep what was selected during character creation, including the starting clothes and weapon, which are stored as equipped items and shown to other players. Equipment stats are not loaded from item data yet.
Created characters also enforce uniqueness in their names, hence the check done
at the end of the character creation flow is actually working. This check is case
insensitive, so Maple and maple can't both exist.
Character deletion works and is instantaneous, so be careful (not as though your characters really matter at this stage of development anyway though).
PICs are currently disabled and will be implemented alongside PINs as mentioned previously. They too will be toggleable via configuration.
Currently, one is able to log into the game world with their selected character. At this point, the client is rerouted to the world server (one of the two binaries built) and loads in with the selected character.
There's not really a whole lot to do in game currently, as very little is actually implemented, however you can still walk around!
New characters initially spawn in the town of Amherst. From here, one may walk around and enter portals to adjacent maps. If the player logs out, they will reappear in the map they logged out from.
Portals are resolved from the map data, so players appear at the portal they came through, and other players see them there.
One may re-assign their keybinds as they normally would with the client. The selected keybinds actually get saved and will be available if the player logs out and back in later.
RustMS is backed by a PostgreSQL server that can be set up using the
docker-compose file in the root of the project. The servers apply the schema
migrations in the db crate themselves on startup.
This demonstration is out-dated and needs to be updated! For now, see the above overview for an idea of the implemented features!
If you would like to run RustMS, start the database, put the v83 NX files in assets/game-data (check them with tools/verify-assets.sh), and run the two servers from the root with cargo run -p rust-ms --bin login and cargo run -p rust-ms --bin world (make sure you have Rust and Cargo installed). You should see something akin to the following:
On the first run of the server, you will likely have more output as dependencies will need to be downloaded and the project itself will build.
The server on its own is not particularly interesting without a client to communicate with it. At this point in time, this server is being developed with the nested HeavenClient project in this repository, however in theory any V83 Maplestory client that's pointed at localhost should work (no promises). From the RustMS repo root, build the client with:
cd HeavenClient
./build-deps.sh
cmake -S . -B cmake-build
cmake --build cmake-build -j"$(nproc)"Note that RustMS supports encryption and hence you shouldn't disable it when building the client.
Once you've built the client, run it:

In the server's console, you should see that the handshake completed successfully and the Login Start packet was received.

At the time of writing this, you can type in a username and password in the client's login window and submit. The server will parse the packet and echo it in the console.

The integration test harness spins up a containerized postgres and pair of login/world servers.
On my machine, I run the harness as follows, using sg to assume the docker user group:
sg docker -c 'cargo run -p integration-harness --bin harnessctl -- test'
- Develop a library for encrypting and decrypting packets in the form that the client expects
- Develop a model for packets and packet IO
- Develop basic infrastructure for sending and receiving packets to/from the client
- Hook up a containerized database
- Implement proper login and account creation flow
- Implement character creation
- Implement a parallel World Server
- Login Sessions to transfer from Login to World Server
- Save character keybinds from in game
- In game portals to move between maps handled
- Revise and update roadmap (in progress)

