msnet

package module
v1.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 26, 2026 License: MIT Imports: 24 Imported by: 0

README

msnet

msnet is MapleStory networking package implemented in Golang

Installation

$ go get github.com/zhyonc/msnet@latest

Quick Start

package main

import (
	"context"
	"fmt"
	"log/slog"
	"net"
	"sync/atomic"
	"time"

	"github.com/zhyonc/msnet"
)

type Server struct {
	addr string
	lis  net.Listener
	idCount atomic.Int32
}

func NewServer(addr string) *Server {
	s := &Server{
		addr:    addr,
	}
	return s
}

func (s *Server) Run() {
	var lc net.ListenConfig
	lis, err := lc.Listen(context.Background(), "tcp", s.addr)
	if err != nil {
		slog.Error("Failed to create tcp listener", "err", err)
		return
	}
	slog.Info("TCPListener is starting on " + s.addr)
	s.lis = lis
	var idCount int32 = 0
	for {
		if s.lis == nil {
			slog.Warn("TCPListener is nil")
			break
		}
		conn, err := s.lis.Accept()
		if err != nil {
			slog.Error("Failed to accept conn", "err", err)
			continue
		}
		slog.Info("New client connected", "addr", conn.RemoteAddr())
		cs := msnet.NewCClientSocket(s, conn, nil, nil)
		go cs.OnRead()
		cs.OnConnect()
		cs.SetID(s.idCount.Add(1))
	}
}

func (s *Server) Shutdown() {
	if s.lis != nil {
		s.lis.Close()
		s.lis = nil
	}
}

func main() {
	msnet.New(&msnet.Setting{
		LocaleRegion:   msnet.GMS,
		MSRegion:       msnet.GMS,
		MSVersion:      95,
		MSMinorVersion: "1",
	})
	s := NewServer("127.0.0.1:8484")
	s.Run()
}

Setting

  • LocaleRegion: Language Regions including EUCKR(KMS)/ShiftJIS(JMS)/GBK(CMS)/Big5(TMS)/Windows1252(GMS)

  • MSRegion: MapleStory Regions including GMSCW(1)/KMS(1)/KMST(2)/JMS(3)/CMS(4)/CMST(5)/TMS(6)/MSEA(7)/GMS(8)/BMS(9)

  • MSVersion: MapleStory Client Version

  • MSMinorVersion: MapleStory Client Minor Version

  • RecvCipherType: Defines how CInPacket are decrypted

    • AESCipher: Used for the majority of clients (default)
    • XORCipher: Used in versions about 2004
  • SendCipherType: Defines how COutPacket are encrypted

    • AESCipher: Used for the majority of clients (default)
    • XORCipher: Used in versions about 2004
    • LinearCipher: Used for server packet data since 2017 (excluding the login server)
    • NullCipher: Used for connect packet
  • DESKey (optional): A 16-byte string used for opcode encryption based on v193-encryption

  • AESKeyType (optional)

    • DefaultKey: Uses the fixed AESKeyDefault for old MSVersion
    • CycleKey: Picks a key from the CycleAESKeys using MSVersion%20
    • CycleKey13: Same as CycleKey, but adds an offset of +13 before modulo 20
  • AESKey (optional): A 32-byte array used for packet data

    • If provided directly, this key is used as‑is
    • If not set (first byte is zero), the key is derived according to the AESKeyType
  • IsTypeHeader1Byte: Used in versions about 2004~2008

  • AESInitType (optional): Compatible with older versions

    • Default: Used in versions after about 2008
    • Duplicate: Used in versions about 2005~2007 (excluding TMS)
    • Shuffle: Used in TMS versions about 2005~2007
  • RecvBuffXOR (optional): The server must use the same XOR key to recover the original packet buffer

  • SendBuffXOR (optional): The client must use the same XOR key to recover the original packet buffer

Packet

Header AESOFB Note
4 Bytes Any Bytes Except for the first packet
Decode

Decode packet length using XOR of two little-endian uint16 values from header
PacketLen = (Header[0]+Header[1]*0x100) ^ (Header[2]+Header[3]*0x100)

Encode

Encode packet length using little-endian XOR with sVersion and sendIV

  • sVersion = (^clientVer >> 8 & 0xFF) | ((^clientVer << 8) & 0xFF00)
  • a = int(sendIV[3])
  • a |= int(sendIV[2])<<8
  • a ^= sVersion
  • b = ((PacketLen << 8) & 0xFF00) | (PacketLen >> 8)
  • c = a ^ b
  • Header = [a>>8, a, c>>8, b]
Format
Opcode Data Note
2 Bytes Any Bytes Except for the connect packet
Connect Packet
Name PacketLen Version MinorVersionLen MinorVersion RecvIV SendIV Region Note
Connect 2 Bytes 2 Bytes 2 Bytes 1 Byte 4 Bytes 4 Bytes 1 Bytes The connect packet

Documentation

Index

Constants

View Source
const (
	LogBackupDir  string = "./log"
	ServerType    string = "login"
	ServerAddr    string = "127.0.0.1:8484"
	HeaderLength  int    = 4
	IVSize        int    = 4
	MaxDataLength int    = 1456
	MaxNameLength int    = 13
)

#region Env

View Source
const (
	GMSCWDESKey string = "G0dD@mnN#H@ckEr!"
	KMSDESKey   string = "G0dD@mnN#H@ckEr!"
	JMSDESKey   string = "M@pl3J@p@nH@ck3r"
	CMSDESKey   string = "aVbTpJ5=ZjG&Db3$"
	TMSDESKey   string = "BrN=r54jQp2@yP6G"
	GMSDESKey   string = "N3x@nGLEUH@ckEr!"
)

#region Cipher

View Source
const FTEpochDiff int64 = 116444736000000000 // (difference between 1601 and 1970 epochs)

Variables

View Source
var (
	STZero         time.Time                                                   // ST 0001-01-01 00:00:00 <-> FT -504911232000000000
	STZeroOverflow time.Time = time.Date(1754, 8, 30, 22, 43, 41, 0, time.UTC) // ST 1754-08-30 22:43:41 <-> FT 48491090210000000
	STSQLMin       time.Time = time.Date(1753, 1, 1, 0, 0, 0, 0, time.UTC)     // ST 1753-01-01 00:00:00 <-> FT 47966688000000000
	STSQLMinDate   time.Time = time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC)     // ST 1000-01-01 00:00:00 <-> FT -189657504000000000
	STStart        time.Time = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)     // ST 1900-01-01 00:00:00 <-> FT 94354848000000000
	STEnd          time.Time = time.Date(2079, 1, 1, 0, 0, 0, 0, time.UTC)     // ST 2079-01-01 00:00:00 <-> FT 150842304000000000
	STEmpty        time.Time = time.Date(0, 0, 0, 0, 0, 0, 0, time.UTC)        // ST 0000-00-00 00:00:00 <-> FT -505255104000000000
)
View Source
var (
	AESKeyDefault = [32]byte{
		0x13, 0x00, 0x00, 0x00,
		0x08, 0x00, 0x00, 0x00,
		0x06, 0x00, 0x00, 0x00,
		0xB4, 0x00, 0x00, 0x00,
		0x1B, 0x00, 0x00, 0x00,
		0x0F, 0x00, 0x00, 0x00,
		0x33, 0x00, 0x00, 0x00,
		0x52, 0x00, 0x00, 0x00,
	}

	CycleAESKeys = [20][32]byte{
		0:  {0x29, 0x00, 0x00, 0x00, 0xE1, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0xF1, 0x00, 0x00, 0x00, 0xB3, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00},
		1:  {0xB3, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00},
		2:  {0x88, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0xDB, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00},
		3:  {0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00},
		4:  {0xF6, 0x00, 0x00, 0x00, 0xE1, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0xB9, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0xB2, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00},
		5:  {0xBF, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00},
		6:  {0x02, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0xCF, 0x00, 0x00, 0x00},
		7:  {0x48, 0x00, 0x00, 0x00, 0xE6, 0x00, 0x00, 0x00, 0xE5, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00},
		8:  {0x18, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00},
		9:  {0x5B, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0x00, 0xE5, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0xA7, 0x00, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00},
		10: {0x76, 0x00, 0x00, 0x00, 0xC9, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xD6, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00},
		11: {0x10, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x9E, 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0x00, 0x9A, 0x00, 0x00, 0x00},
		12: {0x5E, 0x00, 0x00, 0x00, 0x9C, 0x00, 0x00, 0x00, 0x9F, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00},
		13: {0xDA, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00},
		14: {0x0F, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0xC5, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0xF3, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00},
		15: {0x87, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0xDB, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0xAA, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00},
		16: {0xCA, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0xA6, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00},
		17: {0x5E, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00, 0xB4, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 0xCA, 0x00, 0x00, 0x00, 0x9D, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00},
		18: {0xCD, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xAD, 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00},
		19: {0x9F, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0xCA, 0x00, 0x00, 0x00, 0xD3, 0x00, 0x00, 0x00},
	}
)

Functions

func GetLocaleBuf added in v1.1.6

func GetLocaleBuf(s string) []byte

func GetLocaleStr added in v1.1.6

func GetLocaleStr(rawBuf []byte) string

func New

func New(setting *Setting)

func SetLocale added in v1.1.6

func SetLocale(region Region)

func SetLogger

func SetLogger(backupDir string, filename string, level slog.Level, addSource bool) func()

Types

type AESInitType added in v1.1.3

type AESInitType uint8
const (
	Default AESInitType = iota
	Duplicate
	Shuffle
)

type AESKeyType added in v1.1.6

type AESKeyType uint8
const (
	DefaultKey AESKeyType = iota
	CycleKey
	CycleKey13
)

type CClientSocket

type CClientSocket interface {
	SetID(id int32)
	GetID() int32
	GetAddr() string
	XORRecvBuff()
	XORSendBuff()
	OnRead()
	OnConnect()
	InnoHash(cipherType CipherType, dwKey []byte)
	LoopAliveAck(aliveAckSec int)
	OnAliveAck()
	LoopAliveReq(aliveReqSec int, lpAliveReq uint16)
	OnAliveReq(lpAliveReq uint16)
	SetRecvCipherType(cipherType CipherType)
	SetSendCipherType(cipherType CipherType)
	OnOpcodeEncryption(
		lpOpcodeEncryption uint16,
		startOpcode uint16,
		endOpcode uint16,
		isSplit bool,
	)
	DecryptOpcode(randNum uint16) uint16
	SendPacket(oPacket COutPacket)
	Flush()
	OnError(err error)
	Close()
}

func NewCClientSocket

func NewCClientSocket(
	delegate CClientSocketDelegate,
	conn net.Conn,
	rcvIV []byte,
	sndIV []byte,
) CClientSocket

type CClientSocketDelegate added in v1.0.7

type CClientSocketDelegate interface {
	DebugInPacketLog(id int32, iPacket CInPacket)
	DebugOutPacketLog(id int32, oPacket COutPacket)
	NewConnectPacket(
		region Region,
		version uint16,
		minorVersion string,
		seqRcv []byte,
		seqSnd []byte,
	) COutPacket
	ProcessPacket(cs CClientSocket, iPacket CInPacket)
	SocketClose(id int32)
}

type CInPacket

type CInPacket interface {
	DecryptHeader(cipherType CipherType, pBuff []byte)
	DecryptData(cipherType CipherType, dwKey []byte)
	GetType() uint16
	GetRemain() int
	GetOffset() int
	GetLength() int
	DecodeBool() bool
	Decode1() int8
	Decode2() int16
	Decode4() int32
	Decode8() int64
	DecodeBuffer(uSize int) []byte
	DecodeStr() string
	DecodeName(uSize ...int) string
	DecodeTime() uint32
	DecodeDateTime() time.Time
	DumpString(nSize int) string
	Clear()
}

func NewCInPacket

func NewCInPacket(buf []byte) CInPacket

type COutPacket

type COutPacket interface {
	GetType() uint16
	GetSendBuffer() []byte
	GetOffset() int
	GetLength() int
	EncodeBool(b bool)
	Encode1(n int8)
	Encode2(n int16)
	Encode4(n int32)
	Encode8(n int64)
	EncodeBuffer(buf []byte)
	EncodeStr(s string)
	EncodeName(s string, uSize ...int)
	EncodeTime(tTime uint32)
	EncodeDateTime(dTime time.Time)
	EncryptHeader(cipherType CipherType, pBuff []byte, dataLen int, dwKey []byte)
	MakeBufferList(cipherType CipherType, dwKey []byte) []byte
	DumpString(nSize int) string
}

func NewCOutPacket

func NewCOutPacket(nType ...any) COutPacket

type CipherType added in v1.1.3

type CipherType uint8
const (
	AESCipher CipherType = iota
	XORCipher
	LinearCipher
	NullCipher
)

type Region added in v1.1.3

type Region uint8
const (
	GMSCW Region = 1
	KMS   Region = 1
	KMST  Region = 2
	JMS   Region = 3
	CMS   Region = 4
	CMST  Region = 5
	TMS   Region = 6
	MSEA  Region = 7
	GMS   Region = 8
	BMS   Region = 9
)

type Setting

type Setting struct {
	LocaleRegion      Region
	MSRegion          Region
	MSVersion         uint16
	MSMinorVersion    string
	RecvCipherType    CipherType
	SendCipherType    CipherType
	DESKey            string
	AESKeyType        AESKeyType
	AESKey            [32]byte
	IsTypeHeader1Byte bool
	AESInitType       AESInitType
	RecvBuffXOR       uint8
	SendBuffXOR       uint8
}

Directories

Path Synopsis
internal
opcode
Code generated by opcode_test, DO NOT EDIT.
Code generated by opcode_test, DO NOT EDIT.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL