Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

[Dev]Golang login server

Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
I am making version 28 server using the Google Go language. It is currently a work in progress and will be slow as I am learning networking as I go along.

Completed:
- General layout of login server is complete.
- Handshake packet is complete.
- Understanding how the Shanda encryption and implementing it.
- Can now receive packet headers and decrypt the data that follows, as well as recognise the opcode.

Currently working on:
- Thinking of a way to write a handler to separate my server logic from my network code.

For those of you interested -
 
Last edited:
BloopBloop
Joined
Aug 9, 2012
Messages
892
Reaction score
275
short (handshake length)
short version
string sub
byte[] RecvIV
byte[] SendIV
byte locale = (5)
 
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
Thanks. Is the length always at the beggining for each packet sent by the server and client?

I am now sending the size of the packet at the front, as well as corrected the locale packet conversion to byte.

Code:
As bytes - [11 0 40 0 228 48 92 252 43 75 83 137 5]
In hex - [0b 00 28 00 56 f3 2c ef ae ad bf a3 05]

However as soon as the packet is sent I am receiving the selected wrong gateway error message, whereas before it would take a while for that to appear so small amount of progress.

My next question is:

My IV data looks reasonable as does my locale. What is the "string sub" for? As at the moment I have that left as an empty string which becomes zero bytes and so does not get included in the packet.
 
Last edited:
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
Thanks. Is the length always at the beggining for each packet sent by the server and client?

I am now sending the size of the packet at the front, as well as corrected the locale packet conversion to byte.

Code:
As bytes - [11 [URL="tel:0 40 0 228 48 92 252"]0 40 0 228 48 92 252[/URL] [URL="tel:43 75 83 137"]43 75 83 137[/URL] 5]
In hex - [0b [URL="tel:00 28 00 56"]00 28 00 56[/URL] f3 2c ef ae ad bf a3 05]

However as soon as the packet is sent I am receiving the selected wrong gateway error message, whereas before it would take a while for that to appear so small amount of progress.

My next question is:

My IV data looks reasonable as does my locale. What is the "string sub" for? As at the moment I have that left as an empty string which becomes zero bytes and so does not get included in the packet.

The string sub in the handshake packet is the patch location. It's just a "sub version", a mini-patch. Note that this string will have to contain the length (short) and then the characters in bytes, so even if you're sending an empty one, you'd have to write the length (short 0).
 
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
I have noticed that v40b seems to have packet encryption? Are there any versions that do not have any? If so could someone please upload a localhost for that version? I want to get to grips with networking before I handle the encryption side of things since trying to learn too much all at once won't lead me anywhere. Preferably any version before 0.35 would be good.

On a side note is the general packet structure [header][size][data of interest]?
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
I have noticed that v40b seems to have packet encryption? Are there any versions that do not have any? If so could someone please upload a localhost for that version? I want to get to grips with networking before I handle the encryption side of things since trying to learn too much all at once won't lead me anywhere. Preferably any version before 0.35 would be good.

On a side note is the general packet structure [header][size][data of interest]?

First of all, I suggest reading @retep998's page about Packets in the MapleStory Reference Wikia right .

I'm assuming you're using a client you've found on the internet. The public clients for this version (v.40b) have the AES encryption skipped in them. This is because AES was acting a bit weird so it's simply being skipped, therefore leaving the custom encryption only (Shanda, originally taken from China MapleStory). That's the combination (Shanda and AES) that Global MapleStory uses. However, since v.149, Nexon removed Shanda and are only using AES for now. To answer your questions, all versions use a packet encryption.

Note that the Handshake packet is not encrypted, so don't be mislead.

a download for a v.28 client. The address is set by default to 127.0.0.1. It was originally created by @tehkiki.
 
Last edited:
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
Thanks for the client link. That guide is really useful. This is going to require a bit of a re-work for my current server so my progress is going to be slowed down for a bit. The encryption stuff looks quite complex?

REMOVED
 
Last edited:
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
Thanks for the client link. That guide is really useful. This is going to require a bit of a re-work for my current server so my progress is going to be slowed down for a bit. The encryption stuff looks quite complex?

It's actually quite simple. The custom encryption is really easy. You can take a look at OdinMS to get a basic idea, or any publicly released emulator around this section.
 
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
I am struggling to understand the crypto stuff clearly. From what I can gather from the wiki and other sources I have seen is that for received packets the process is as follows:

Remove AES -> Remove custom maple encryption -> Generate a new decrypt IV.

Therefore I have implemented what I believe to be the correct implementation for removing the custom maple encryption (see end of post) and do not need to worry about the AES encryption as the client Fraysa has provided has that removed.

However when receiving a packet such as the login password check and applying the algorithm below I produce a byte array of zeros.

What have I misunderstood about the decryption process?

Remove maple encryption based off of Diamondo's javascript code:
Code:
func DecryptPacket(packet []byte) []byte {
	length := len(packet)
	var j int
	var a, b, c byte

	for i := 0; i < 3; i++ {
		a = 0
		b = 0

		for j = length; j > 0; j-- {
			c = packet[j - 1]
			c = RollLeft(c, 3)
			c ^= 0x13
			a = c
			c ^= b
			c = c - byte(j)
			c &= 0xFF
			c = RollRight(c, 4)
			b = a
			packet[j - 1] = c
		}

		a = 0
		b = 0

		for j = length; j > 0; j-- {
			c = packet[length - j]
			c -= 0x48
			c &= 0xFF
			c = RollLeft(c, j)
			a = c
			c ^= b
			c = c - byte(j)
			c &= 0xFF
			c = RollRight(c, 3)
			b = a
			packet[length - j] = c
		}
	}

	return packet
}
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
I am struggling to understand the crypto stuff clearly. From what I can gather from the wiki and other sources I have seen is that for received packets the process is as follows:

Remove AES -> Remove custom maple encryption -> Generate a new decrypt IV.

Therefore I have implemented what I believe to be the correct implementation for removing the custom maple encryption (see end of post) and do not need to worry about the AES encryption as the client Fraysa has provided has that removed.

However when receiving a packet such as the login password check and applying the algorithm below I produce a byte array of zeros.

What have I misunderstood about the decryption process?

Remove maple encryption based off of Diamondo's javascript code:
Code:
func DecryptPacket(packet []byte) []byte {
    length := len(packet)
    var j int
    var a, b, c byte

    for i := 0; i < 3; i++ {
        a = 0
        b = 0

        for j = length; j > 0; j-- {
            c = packet[j - 1]
            c = RollLeft(c, 3)
            c ^= 0x13
            a = c
            c ^= b
            c = c - byte(j)
            c &= 0xFF
            c = RollRight(c, 4)
            b = a
            packet[j - 1] = c
        }

        a = 0
        b = 0

        for j = length; j > 0; j-- {
            c = packet[length - j]
            c -= 0x48
            c &= 0xFF
            c = RollLeft(c, j)
            a = c
            c ^= b
            c = c - byte(j)
            c &= 0xFF
            c = RollRight(c, 3)
            b = a
            packet[length - j] = c
        }
    }

    return packet
}

This is the process, listed below. I also added a comment so you know what each thing does.

When the data arrives, you have to check if it contains a header, first. I'm pretty sure you already implemented this. Anyways, after the handshake packet is received, all the packets are encrypted. Be sure to check if you received the initial packet so you can start decrypting the other ones.

1. Generate a new IV code for the AES and header generation. You basically override the old IV and generate a new one (Code is in C#).

Code:
byte[] newIV = new byte[] { 0xF2, 0x53, 0x50, 0xC6 };
            for (var i = 0; i < 4; i++)
            {
                byte input = pOldIV[i];
                byte tableInput = sShiftKey[input];
                newIV[0] += (byte)(sShiftKey[newIV[1]] - input);
                newIV[1] -= (byte)(newIV[2] ^ tableInput);
                newIV[2] ^= (byte)(sShiftKey[newIV[3]] + input);
                newIV[3] -= (byte)(newIV[0] - tableInput);


                uint val = BitConverter.ToUInt32(newIV, 0);
                uint val2 = val >> 0x1D;
                val <<= 0x03;
                val2 |= val;
                newIV[0] = (byte)(val2 & 0xFF);
                newIV[1] = (byte)((val2 >> 8) & 0xFF);
                newIV[2] = (byte)((val2 >> 16) & 0xFF);
                newIV[3] = (byte)((val2 >> 24) & 0xFF);
            }
            Buffer.BlockCopy(newIV, 0, pOldIV, 0, 4);

2. Use the decryption code for the Shanda encryption. I've compared yours and it's indentical, so it should work. Make sure you read the header length and contents correctly. I suggest debugging your program when you receive the login packet and following the process one by one to see what breaks in the way.
 
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
When the data arrives, you have to check if it contains a header, first. I'm pretty sure you already implemented this. Anyways, after the handshake packet is received, all the packets are encrypted. Be sure to check if you received the initial packet so you can start decrypting the other ones.

I think this is were my issue lies. I send the handshake packet and then my server starts listening. If it receives data then it goes and deals with it. However the first time I receive data is when I try to login. I must have missed the handshake from the client to the server then?
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
I think this is were my issue lies. I send the handshake packet and then my server starts listening. If it receives data then it goes and deals with it. However the first time I receive data is when I try to login. I must have missed the handshake from the client to the server then?

Bleh, sorry, I messed up. The initial packet is sent from the server, I thought for a second you were making a client. Anyways, I think that your header generation is wrong. Would you mind posting it? (Or post the whole process). I also suggest you create a Git project for this.
 
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
I made a git repo for the project - (git seems to slightly mess with the formatting). I am currently not generating headers as I am trying to decrypt the incoming packet from the client so I have implemented the Decrypt function like in the Wvs server.

Few things to note about the packages:
MapleHeader contains a list of headers
MapleLoginSession controls each connected client session
MaplePacket handles the packets thrown to it by MapleLoginSession
Packet is a packet object

Once I have got decryption, encrpytion and database connection finished I will be re-writing the code to be more efficient. to allow re-usability accross login, channel and cash shop? servers.

P.S. Thanks for help Fraysa definitely could not have gotten this far without it.
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
Hey again, Hucaru.

No problem at all, I'm glad to help. I really like people who try to make a change and attempt to create emulators in another language other than Java or OdinMS. That's a cool project.

Anyways, back to topic.

I think the problem lays with your reading mechanism. When you read data from the client, the inital length should be 4. Those 4 bytes contain information about the packet that the client sent (The length of the data, the version, and some IV information). Here's a method that returns the length of the packet from the header (Credits to @Diamondo25).

Code:
private static int GetHeaderLength(byte[] buffer)
        {
            int length = (int)buffer[0] |
                         (int)(buffer[1] << 8) |
                         (int)(buffer[2] << 16) |
                         (int)(buffer[3] << 24);
            length = (length >> 16) ^ (length & 0xFFFF);
            return (ushort)length;
        }

After you got the length of the packet, you have to use that length with your reading mechanism again, so you know the exact amount of bytes to read. Then, you just update the IV and decrypt the bytes.

I suggest creating a method for your reading mechanism that requires a length of the buffer (and probably a boolean to indicate if you're reading a header or not), and a method that handles the callback of that reading mechanism. The first one will read a buffer from the session with the specified length and will return the data, and the callback will handle it.

The callback method will check if the callback is from a header reading, or from the data reading. If it's from the header, you generate the length (using the method I gave you) and read according to the length. If it's the data, you simply update the IV and decrypt it.

Good luck. Post anything here!

EDIT: I also noticed that you send a response for every packet request from the client. That's not necessary as not every packet requires a response, and some responses are sent to the whole character inside the map, not only the client.
 
Last edited:
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
I think I understand it better now. I will most likely now rewrite everything from scratch as when I first started I was blindly making this and so the code has become convoluted and is starting to get irritating to manage. If I have understood everything correctly this is how to handle data sent from the client:

client sends 4 bytes (these bytes contain IV data, client version, as well as the size of the 'meaningful' data) (are these encrypted using the shanda encryption as well?) -> the next set of bytes that are as long as the previous 4 bytes dictated contains information pertaining to the game (pData) -> Decrypt the shanda encryption from these packets.

the response from the server is then in a mirror format to the received data with the header being generated by creating a new IV.

Furthermore depending on opcode the response might need to be sent to multiple clients
 
Joined
Apr 10, 2008
Messages
4,087
Reaction score
1,264
The header (that contains the data about the packet) is not encrypted.

For the responses, I mean that some responses are sent for the whole map that the character is in. For example, the client sends a mob movement packet to one client. The handler will send the mob movement response to all of the clients inside the map, instead of handling one by one, it's easier.
 
Newbie Spellweaver
Joined
Jan 23, 2010
Messages
17
Reaction score
6
The header (that contains the data about the packet) is not encrypted.

For the responses, I mean that some responses are sent for the whole map that the character is in. For example, the client sends a mob movement packet to one client. The handler will send the mob movement response to all of the clients inside the map, instead of handling one by one, it's easier.

Thanks. I will try and implement a handler. However, while I understand what you mean conceptually I would not know how to implement it. For now I will concentrate on getting to grips with just a login server and get comfortable with packets and the like and then move onto thinking about multiplayer aspects as well as server to server connectivity

Edit:

I have implemented a function to retrieve the length to read from the header:
Code:
func GetHeaderLength(data []byte) uint16 {
	var length uint16 = uint16(data[0]) | 
					 uint16(data[1] << 8) |
					 uint16(data[2] << 16) |
					 uint16(data[3] << 24)

	return (length >> 16) ^ (length & 0xFFFF)
}

However it seems to just always chose the first byte of the header as the length to read next. E.g. I will receive a header of - [77 212 102 212] and the GenerateHeaderLength function returns 77, which is too long.
 
Last edited:
Elite Diviner
Joined
Apr 7, 2008
Messages
494
Reaction score
66
Thanks. I will try and implement a handler. However, while I understand what you mean conceptually I would not know how to implement it. For now I will concentrate on getting to grips with just a login server and get comfortable with packets and the like and then move onto thinking about multiplayer aspects as well as server to server connectivity

Edit:

I have implemented a function to retrieve the length to read from the header:
Code:
func GetHeaderLength(data []byte) uint16 {
    var length uint16 = uint16(data[0]) | 
                     uint16(data[1] << 8) |
                     uint16(data[2] << 16) |
                     uint16(data[3] << 24)

    return (length >> 16) ^ (length & 0xFFFF)
}

However it seems to just always chose the first byte of the header as the length to read next. E.g. I will receive a header of - [77 212 102 212] and the GenerateHeaderLength function returns 77, which is too long.

Hi Hucaru

well It is very important you check the packet to make sure it has a header otherwise just disconnect the session after which onces you confirm you have the header

this is pretty much how it is like in current version and back in v55

Code:
[COLOR=#000000]@Override
[/COLOR][COLOR=#000000]	protected boolean doDecode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception {[/COLOR]		MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY);
		DecoderState decoderState = (DecoderState) session.getAttribute(DECODER_STATE_KEY);

		if (decoderState == null) {
			decoderState = new DecoderState();
			session.setAttribute(DECODER_STATE_KEY, decoderState);
		}
		
		if (in.remaining() >= 4 && decoderState.packetlength == -1) {
			int packetHeader = in.getInt();
			if (!client.getReceiveCrypto().checkPacket(packetHeader)) {
				log.warn(MapleClient.getLogMessage(client, "Client failed packet check -> disconnecting"));
				session.close();
				return false;
			}
			decoderState.packetlength = MapleAESOFB.getPacketLength(packetHeader);
		} else if (in.remaining() < 4 && decoderState.packetlength == -1) {
			log.trace("decode... not enough data");
			return false;
		}

		if (in.remaining() >= decoderState.packetlength) {
			byte decryptedPacket[] = new byte[decoderState.packetlength];
			in.get(decryptedPacket, 0, decoderState.packetlength);
			decoderState.packetlength = -1;
			
			client.getReceiveCrypto().crypt(decryptedPacket);
			MapleCustomEncryption.decryptData(decryptedPacket);
			out.write(decryptedPacket);
			
			return true;
		} else {
			log.trace("decode... not enough data to decode (need {})", decoderState.packetlength);
			return false;
		}
	}

}
 
Back
Top