tolower breaking utf8
M2Dev Client Source
This repository contains the source code necessary to compile the game client executable.
How to build (short version)
cmake -S . -B build
cmake --build build
For more installation/configuration, check out the instructions below.
📋 Changelog
Encryption & Security Overhaul
The entire legacy encryption system has been replaced with libsodium.
Removed Legacy Crypto
- Crypto++ (cryptopp) vendor library — Completely removed from the project
- Panama cipher (
CFilterEncoder,CFilterDecoder) — Removed fromNetStream - TEA encryption (
tea.h,tea.cpp) — Removed from both client and server - DH2 key exchange (
cipher.h,cipher.cpp) — Removed fromEterBase - Camellia cipher — Removed all references
_IMPROVED_PACKET_ENCRYPTION_— Entire system removed (XTEA key scheduling, sequence encryption, key agreement)adwClientKey[4]— Removed from all packet structs (TPacketCGLogin2,TPacketCGLogin3,TPacketGDAuthLogin,TPacketGDLoginByKey,TPacketLoginOnSetup) and all associated code on both client and serverLSS_SECURITY_KEY— Dead code removed ("testtesttesttest"hardcoded key,GetSecurityKey()function)
New Encryption System (libsodium)
- X25519 key exchange —
SecureCipherclass handles keypair generation and session key derivation viacrypto_kx_client_session_keys/crypto_kx_server_session_keys - XChaCha20-Poly1305 AEAD — Used for authenticated encryption of handshake tokens (key exchange, session tokens)
- XChaCha20 stream cipher — Used for in-place network buffer encryption via
EncryptInPlace()/DecryptInPlace()(zero overhead, nonce-counter based replay prevention) - Challenge-response authentication — HMAC-based (
crypto_auth) verification during key exchange to prove shared secret derivation - New handshake protocol —
HEADER_GC_KEY_CHALLENGE/HEADER_CG_KEY_RESPONSE/HEADER_GC_KEY_COMPLETEpacket flow for secure session establishment
Network Encryption Pipeline
- Client send path — Data is encrypted at queue time in
CNetworkStream::Send()(prevents double-encryption on partial TCP sends) - Client receive path — Data is decrypted immediately after
recv()in__RecvInternalBuffer(), before being committed to the buffer - Server send path — Data is encrypted in
DESC::Packet()viaEncryptInPlace()after encoding to the output buffer - Server receive path — Newly received bytes are decrypted in
DESC::ProcessInput()viaDecryptInPlace()before buffer commit
Login Security Hardening
- Removed plaintext login path —
HEADER_CG_LOGIN(direct password to game server) has been removed. All game server logins now require a login key obtained through the auth server (HEADER_CG_LOGIN2/LoginByKey) - CSPRNG login keys —
CreateLoginKey()now usesrandombytes_uniform()(libsodium) instead of the non-cryptographic Xoshiro128PlusPlus PRNG - Single-use login keys — Keys are consumed (removed from the map) immediately after successful authentication
- Shorter key expiry — Expired login keys are cleaned up after 15 seconds (down from 60 seconds). Orphaned keys (descriptor gone, never expired) are also cleaned up
- Login rate limiting — Per-IP tracking of failed login attempts. After 5 failures within 60 seconds, the IP is blocked with a
BLOCKstatus and disconnected. Counter resets after cooldown or successful login - Removed Brazil password bypass — The
LC_IsBrazil()block that unconditionally disabled password verification has been removed
Pack File Encryption
- libsodium-based pack encryption —
PackLibnow uses XChaCha20-Poly1305 for pack file encryption, replacing the legacy Camellia/XTEA system - Secure key derivation — Pack encryption keys are derived using
crypto_pwhash(Argon2id)
Networking Modernization Roadmap
A 5-phase modernization of the entire client/server networking stack — packet format, buffer management, handshake protocol, connection architecture, and packet dispatching. Every phase is complete and verified on both client and server.
Phase 1 — Packet Format + Buffer System + Memory Safety
Replaced the legacy 1-byte packet headers and raw C-style buffers with a modern, uniform protocol.
What changed
- 2-byte headers + 2-byte length prefix — All packet types (
CG::,GC::,GG::,GD::,DG::) now useuint16_theader +uint16_tlength. This increases the addressable packet space from 256 to 65,535 unique packet types and enables safe variable-length parsing - Namespaced packet headers — All headers moved from flat
HEADER_CG_*defines to C++ namespaces:CG::MOVE,GC::PING,GG::LOGIN,GD::PLAYER_SAVE,DG::BOOT. Subheaders similarly namespaced:GuildSub::GC::LOGIN,ShopSub::CG::BUY, etc. - RAII RingBuffer — All raw
buffer_t/LPBUF/new[]/delete[]patterns replaced with a singleRingBufferclass featuring lazy compaction at 50% read position, exponential growth, and inlined accessors - PacketReader / PacketWriter — Type-safe helpers that wrap buffer access with bounds checking, eliminating raw pointer arithmetic throughout the codebase
- Sequence system modernized — Packet sequence tracking retained for debugging but fixed to byte offset 4 (after header + length)
- SecureCipher — XChaCha20-Poly1305 stream cipher for all post-handshake traffic (see Encryption section above)
What was removed
buffer.h/buffer.cpp(legacy C buffer library)- All
LPBUF,buffer_new(),buffer_delete(),buffer_read(),buffer_write()calls - Raw
new[]/delete[]buffer allocations in DESC classes - 1-byte header constants (
HEADER_CG_*,HEADER_GC_*, etc.)
Why
The legacy 1-byte header system limited the protocol to 256 packet types (already exhausted), raw C buffers had no bounds checking and were prone to buffer overflows, and the flat namespace caused header collisions between subsystems.
Phase 2 — Modern Buffer System (merged into Phase 1)
All connection types now use RingBuffer uniformly.
What changed
- All DESC types (
DESC,CLIENT_DESC,DESC_P2P) useRingBufferform_inputBuffer,m_outputBuffer, andm_bufferedOutputBuffer - PeerBase (db layer) ported to
RingBuffer - TEMP_BUFFER (local utility for building packets) backed by
RingBuffer
What was removed
libthecore/buffer.handlibthecore/buffer.cpp— the entire legacy buffer library
Why
The legacy buffer system used separate implementations across different connection types, had no RAII semantics (manual malloc/free), and offered no protection against buffer overflows.
Phase 3 — Simplified Handshake
Replaced the legacy multi-step handshake (4+ round trips with time synchronization and UDP binding) with a streamlined 1.5 round-trip flow.
What changed
- 1.5 round-trip handshake — Server sends
GC::KEY_CHALLENGE(with embedded time sync), client responds withCG::KEY_RESPONSE, server confirms withGC::KEY_COMPLETE. Session is encrypted from that point forward - Time sync embedded — Initial time synchronization folded into
GC::KEY_CHALLENGE; periodic time sync handled byGC::PING/CG::PONG - Handshake timeout — 5-second expiry on handshake phase; stale connections are automatically cleaned up in
DestroyClosed()
What was removed
- 6 dead packet types:
CG_HANDSHAKE,GC_HANDSHAKE,CG_TIME_SYNC,GC_TIME_SYNC,GC_HANDSHAKE_OK,GC_BINDUDP - Server functions:
StartHandshake(),SendHandshake(),HandshakeProcess(),CreateHandshake(),FindByHandshake(),m_map_handshake - Client functions:
RecvHandshakePacket(),RecvHandshakeOKPacket(),m_HandshakeData,SendHandshakePacket() - ~12 server files and ~10 client files modified
Why
The original handshake required 4+ round trips, included dead UDP binding steps, had no timeout protection (stale connections could linger indefinitely), and the time sync was a separate multi-step sub-protocol that added latency to every new connection.
Phase 4 — Unified Connection (Client-Side Deduplication)
Consolidated duplicated connection logic into the base CNetworkStream class.
What changed
- Key exchange (
RecvKeyChallenge/RecvKeyComplete) moved from 4 separate implementations toCNetworkStreambase class - Ping/pong (
RecvPingPacket/SendPongPacket) moved from 3 separate implementations toCNetworkStreambase class - CPythonNetworkStream overrides
RecvKeyChallengeonly for time sync, delegates all crypto to base - CGuildMarkDownloader/Uploader —
RecvKeyCompleteAndLoginwraps base + sendsCG::MARK_LOGIN - CAccountConnector — Fixed raw
crypto_aeadbug (now uses base classcipher.DecryptToken) - Control-plane structs extracted to
EterLib/ControlPackets.h(Phase, Ping, Pong, KeyChallenge, KeyResponse, KeyComplete) - CGuildMarkUploader —
m_pbySymbolBufmigrated fromnew[]/delete[]tostd::vector<uint8_t>
What was removed
- ~200 lines of duplicated code across
CAccountConnector,CGuildMarkDownloader,CGuildMarkUploader, andCPythonNetworkStream
Why
The same key exchange and ping/pong logic was copy-pasted across 3-4 connection subclasses, leading to inconsistent behavior (the CAccountConnector had a raw crypto bug), difficult maintenance, and unnecessary code volume.
Phase 5 — Packet Handler Registration / Dispatcher
Replaced giant switch statements with std::unordered_map dispatch tables for O(1) packet routing.
What changed
Client:
CPythonNetworkStream— Phase-specific handler maps for Game, Loading, Login, Select, and Handshake phases- Registration pattern:
m_gameHandlers[GC::MOVE] = &CPythonNetworkStream::RecvCharacterMovePacket; - Dispatch:
DispatchPacket(m_gameHandlers)— reads header, looks up handler, calls it
Server:
CInputMain,CInputDead,CInputAuth,CInputLogin,CInputP2P,CInputHandshake,CInputDB— all converted to dispatch tablesCInputDBuses 3 template adapters (DataHandler,DescHandler,TypedHandler) + 14 custom adapters for the diverse DB callback signatures
What was removed
- All
switch (header)blocks across 7 server input processors and 5 client phase handlers - ~3,000 lines of switch/case boilerplate
Why
The original dispatch used switch statements with 50-100+ cases each. Adding a new packet required modifying a massive switch block, which was error-prone and caused merge conflicts. The table-driven approach enables O(1) lookup, self-documenting handler registration, and trivial addition of new packet types.
Post-Phase 5 Cleanup
Follow-up tasks after the core roadmap was complete.
One-Liner Adapter Reformat
- 24 adapter methods across 4 server files (
input_main.cpp,input_p2p.cpp,input_auth.cpp,input_login.cpp) reformatted from single-line to multi-line for readability
MAIN_CHARACTER Packet Merge
- 4 mutually exclusive packets (
GC::MAIN_CHARACTER,MAIN_CHARACTER2_EMPIRE,MAIN_CHARACTER3_BGM,MAIN_CHARACTER4_BGM_VOL) merged into a single unifiedGC::MAIN_CHARACTERpacket - Single struct always includes BGM fields (zero when unused — 29 extra bytes on a one-time-per-load packet)
- 4 nearly identical client handlers merged into 1
- 3 redundant server send paths merged into 1
UDP Leftover Removal
- 7 client files deleted:
NetDatagram.h/.cpp,NetDatagramReceiver.h/.cpp,NetDatagramSender.h/.cpp,PythonNetworkDatagramModule.cpp - 8 files edited: Removed dead stubs (
PushUDPState,initudp,netSetUDPRecvBufferSize,netConnectUDP), declarations, and Python method table entries - Server: Removed
socket_udp_read(),socket_udp_bind(),__UDP_BLOCK__define
Subheader Dispatch
- Extended the Phase 5 table-driven pattern to subheader switches with 8+ cases
- Client: Guild (19 sub-handlers), Shop (10), Exchange (8) in
PythonNetworkStreamPhaseGame.cpp - Server: Guild (15 sub-handlers) in
input_main.cpp - Small switches intentionally kept as-is: Messenger (5), Fishing (6), Dungeon (2), Server Shop (4)
Performance Audit & Optimization
Comprehensive audit of all Phase 1-5 changes to identify and eliminate performance overhead.
Debug Logging Cleanup
- Removed all hot-path
TraceError/Tracef/sys_logfrom networking code on both client and server - Client:
NetStream.cpp,SecureCipher.cpp,PythonNetworkStream*.cpp— eliminated per-frame and per-packet traces that caused disk I/O every frame - Server:
desc.cpp,input.cpp,input_login.cpp,input_auth.cpp,SecureCipher.cpp— eliminated[SEND],[RECV],[CIPHER]logs that fired on every packet
Packet Processing Throughput
MAX_RECV_COUNT4 → 32 — Game phase now processes up to 32 packets per frame (was 4, severely limiting entity spawning on map entry)- Loading phase while-loop — Changed from processing 1 packet per frame to draining all available packets, making phase transitions near-instant
Flood Check Optimization
- Replaced
get_dword_time()withthecore_pulse()inDESC::CheckPacketFlood()— eliminates agettimeofday()syscall on every single packet received.thecore_pulse()is cached once per game-loop iteration
Flood Protection
- Per-IP connection limits — Configurable maximum connections per IP address (
flood_max_connections_per_ip, default: 10) - Global connection limits — Configurable maximum total connections (
flood_max_global_connections, default: 8192) - Per-second packet rate limiting — Connections exceeding
flood_max_packets_per_sec(default: 300) are automatically disconnected - Handshake timeout — 5-second expiry prevents connection slot exhaustion from incomplete handshakes
Pre-Phase 3 Cleanup
Preparatory cleanup performed before the handshake simplification.
- File consolidation — Merged scattered packet definitions into centralized header files
- Alias removal — Removed legacy
#definealiases that mapped old names to new identifiers - Monarch system removal — Completely removed the unused Monarch (emperor) system from both client and server, including all related packets, commands, quest functions, and UI code
- TrafficProfiler removal — Removed the
TrafficProfilerclass and all references (unnecessary runtime overhead) - Quest management stub removal — Removed empty
questlua_mgmt.cpp(monarch-era placeholder with no functions)
Summary of Removed Legacy Systems
A consolidated reference of all legacy systems, files, and dead code removed across the entire modernization effort.
| System | What was removed | Replaced by |
|---|---|---|
| Legacy C buffer | buffer.h, buffer.cpp, all LPBUF/buffer_new()/buffer_delete() calls, raw new[]/delete[] buffer allocations |
RAII RingBuffer class |
| 1-byte packet headers | All HEADER_CG_*, HEADER_GC_*, HEADER_GG_*, HEADER_GD_*, HEADER_DG_* defines |
2-byte namespaced headers (CG::, GC::, GG::, GD::, DG::) |
| Old handshake protocol | 6 packet types (CG_HANDSHAKE, GC_HANDSHAKE, CG_TIME_SYNC, GC_TIME_SYNC, GC_HANDSHAKE_OK, GC_BINDUDP), all handshake functions and state |
1.5 round-trip key exchange (KEY_CHALLENGE/KEY_RESPONSE/KEY_COMPLETE) |
| UDP networking | 7 client files (NetDatagram*.h/.cpp, PythonNetworkDatagramModule.cpp), server socket_udp_read()/socket_udp_bind()/__UDP_BLOCK__ |
Removed entirely (game is TCP-only) |
| Old sequence system | m_seq, SetSequence(), GetSequence(), old sequence variables |
Modernized sequence at fixed byte offset 4 |
| TrafficProfiler | TrafficProfiler class and all references |
Removed entirely |
| Monarch system | All monarch/emperor packets, commands (do_monarch_*), quest functions (questlua_monarch.cpp, questlua_mgmt.cpp), UI code, GM commands |
Removed entirely (unused feature) |
| Legacy crypto | Crypto++, Panama cipher, TEA, DH2, Camellia, XTEA, adwClientKey[4], LSS_SECURITY_KEY |
libsodium (X25519 + XChaCha20-Poly1305) |
| Switch-based dispatch | Giant switch (header) blocks (50-100+ cases each) across 7 server input processors and 5 client phase handlers |
std::unordered_map dispatch tables |
| Duplicated connection code | Key exchange and ping/pong copy-pasted across 3-4 client subclasses | Consolidated in CNetworkStream base class |
Installation/Configuration
This is the third part of the entire project and it's about the client binary, the executable of the game.
Below you will find a comprehensive guide on how to configure all the necessary components from scratch.
This guide is made using a Windows environment as the main environment and cannot work in non-Windows operating systems!
This guide also uses the latest versions for all software demonstrated as of its creation date at February 4, 2026.
© All copyrights reserved to the owners/developers of any third party software demonstrated in this guide other than this project/group of projects.
📋 Order of projects configuration
If one or more of the previous items is not yet configured please come back to this section after you complete their configuration steps.
- ✅ M2Dev Server Source
- ✅ M2Dev Server
- ▶️ M2Dev Client Source [YOU ARE HERE]
- ⏳ M2Dev Client [ALSO CONTAINS ADDITIONAL INFORMATION FOR POST-INSTALLATION STEPS]
🧱 Software Prerequisites
Please make sure that you have installed the following software in your machine before continuing:
Visual Studio: The software used to edit and compile the source code. Download
Visual Studio Code (VS Code): A lighter alternative to Visual Studio, harder to build the project in this software but it is recommended for code editing. Download
Git: Used to clone the repositories in your Windows machine. Download
CMake: Required for setting up and configuring the build of the source code. Download
Notepad++ (optional but recommended): Helps with quick, last minute edits. Download
👁️ Required Visual Studio packages
Make sure you have installed these packages with Visual Studio Installer in order to compile C++ codebases:
Packages
Note: Windows 11 SDK's can be replaced by Windows 10 SDK's, but it is recommended to install one of them.
⬇️ Obtaining the Client Source
To build the source for the first time, you first need to clone it. In your command prompt, cd into your desired location or create a new folder wherever you want and download the project using Git.
Here's how
Open up your terminal inside or
cdinto your desired folder and type this command:git clone https://github.com/d1str4ught/m2dev-client-src.git
✅ You have successfully obtained the Client Source project!
🛠️ Building the Source Code
Building the project is extremely simple, if all Visual Studio components are being installed correctly.
Instructions
Open up your terminal inside, or
cdin your project's root working directory and initialize the build with this command:cmake -S . -B buildA new
buildfolder has been created in your project's root directory. This folder contains all the build files and configurations, along with theslnfile to open the project in Visual Studio.Double click on that file to launch Visual Studio and load the project.
In the Solution Explorer, select all the projects minus the container folders, right click on one of the selected items, and click Properties
Next, make sure that the following settings are adjusted like this:
- Windows SDK Version should be the latest of Windows 10. It is not recommended to select any Windows 11 versions yet if avalable.
- Platform Toolset is the most important part for your build to succeed! Select the highest number you see. v145 is for Visual Studio 2026. If you are running Visual Studio 2022 you won't have that, you will have v143, select that one, same goes for older Visual Studio versions.
- C++ Language Standard should be C++20 as it is the new standard defined in the CMakeList.txt files as well. Might as well set it like that for all dependencies.
- C Language Standard should be C17 as it is the new standard defined in the CMakeList.txt files as well. Might as well set it like that for all dependencies.
Once done, click Apply and then OK to close this dialog.
After that, in the toolbar at the top of the window, select your desired output configuration:
Finally, click on the Build option at the top and select Build Solution, or simply press CTRL+SHIFT+B in your keyboard with all the projects selected.
Note: if this is NOT your first build after executing the
cmake -S . -B buildcommand for this workspace, it is recommended to click Clean Solution before Build Solution.
Where to find your compiled binaries:
Inside the build folder in your cloned repository, you should have a bin folder and inside that, you should have a Debug, Release, RelWithDebInfo or MinSizeRel folder, depending on your build configuration selection.
In that folder you should be seeing all your binaries:
If you did NOT install the Client project yet, you are done here.
If you HAVE the Client project installed, paste these 2
.exefiles in these locations inside the Server project:
- Metin2_<Debug|Release|RelWithDebInfo|MinSizeRel>.exe: inside root folder of the Client project
- PackMaker.exe: inside
assets\PackMaker.exe
✅ You have successfully built the Client Source!
🔥 The Client Source part of the guide is complete!
Next steps
You should now be finally ready to proceed to Client project packing and entering the game for the first time!
⭐ NEW: We are now on Discord, feel free to check us out!













