RuneHive-Tarnish
Neural OSRS Enhancement Framework
Loading...
Searching...
No Matches
LoginDecoder.java
1package com.osroyale.net.codec.login;
2
3import com.osroyale.Config;
4import com.osroyale.net.codec.IsaacCipher;
5import com.osroyale.net.session.LoginSession;
6import io.netty.buffer.ByteBuf;
7import io.netty.channel.Channel;
8import io.netty.channel.ChannelFutureListener;
9import io.netty.channel.ChannelHandlerContext;
10import io.netty.handler.codec.ByteToMessageDecoder;
11import org.apache.logging.log4j.LogManager;
12import org.apache.logging.log4j.Logger;
13import org.jire.tarnishps.ByteBufUtil;
14
15import java.math.BigInteger;
16import java.net.InetSocketAddress;
17import java.security.SecureRandom;
18import java.util.List;
19import java.util.Random;
20
63
64 * The class that handles a connection through the login protocol.
65 *
66 * @author nshusa
67 */
68public final class LoginDecoder extends ByteToMessageDecoder {
69
70 private static final Logger logger = LogManager.getLogger(LoginDecoder.class);
71
72 private static final int LOGIN_HANDSHAKE = 14;
73 private static final int NEW_CONNECTION_OPCODE = 16;
74 private static final int RECONNECTION_OPCODE = 18;
75 private static final int MAGIC_NUMBER = 255;
76 private static final int LOGIN_BLOCK_HEADER_SIZE = 38;
77
78 private static final ThreadLocal<Random> RANDOM = ThreadLocal.withInitial(SecureRandom::new);
79
80 private State state = State.HANDSHAKE;
81
82 @Override
83 protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
84 switch (state) {
85
86 case HANDSHAKE:
87 decodeHandshake(ctx, in);
88 state = State.CONNECTION_TYPE;
89 break;
90
91 case CONNECTION_TYPE:
92 decodeConnectionType(ctx, in);
93 state = State.PAYLOAD;
94 break;
95
96 case PAYLOAD:
97 decodePayload(ctx, in, out);
98 break;
99
100 }
101 }
102
103 private void decodeHandshake(ChannelHandlerContext ctx, ByteBuf in) {
104 final Channel channel = ctx.channel();
105 channel.attr(Config.SESSION_KEY).set(new LoginSession(channel));
106
107 if (in.readableBytes() >= 2) {
108 final int handshake = in.readUnsignedByte();
109
110 if (handshake != LOGIN_HANDSHAKE) {
111 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
112 return;
113 }
114
115 @SuppressWarnings("unused") final int nameHash = in.readUnsignedByte();
116 final long serverSeed = RANDOM.get().nextLong();
117
118 ByteBuf buf = ctx.alloc().buffer(17);
119 buf.writeLong(0);
120 buf.writeByte(0);
121 buf.writeLong(serverSeed);
122 ctx.writeAndFlush(buf, ctx.voidPromise());
123 } else {
124 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
125 }
126 }
127
128 private void decodeConnectionType(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
129 if (in.isReadable()) {
130 final int connectionType = in.readUnsignedByte();
131
132 if (connectionType != NEW_CONNECTION_OPCODE && connectionType != RECONNECTION_OPCODE) {
133 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
134 }
135 } else {
136 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
137 }
138 }
139
140 private void decodePayload(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
141 final String host = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
142
143 final int loginBlockSize = in.readUnsignedByte();
144 if (in.isReadable(loginBlockSize)) {
145 final int magicId = in.readUnsignedByte();
146
147 if (magicId != MAGIC_NUMBER) {
148 logger.warn(String.format("[%s] wrong magic id: %d", host, magicId));
149 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
150 return;
151 }
152
153 final int clientVersion = in.readUnsignedByte();
154 if (Config.CLIENT_VERSION != clientVersion) {
155 logger.warn("[{}] outdated client(client version): {} should be: {}", host, clientVersion, Config.CLIENT_VERSION);
156 sendResponseCode(ctx, LoginResponse.GAME_UPDATED);
157 return;
158 }
159
160 final int memoryVersion = in.readUnsignedByte();
161 if (memoryVersion != 0 && memoryVersion != 1) {
162 logger.warn(String.format("[%s] wrong memory version: %d", host, memoryVersion));
163 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
164 return;
165 }
166
167 final int[] crcs = new int[9];
168
169 for (int index = 0; index < crcs.length; index++) {
170 crcs[index] = in.readInt();
171 }
172
173 final int expectedSize = in.readUnsignedByte(); // rsa header
174
175 if (expectedSize != loginBlockSize - LOGIN_BLOCK_HEADER_SIZE) {
176 logger.warn(String.format("[%s] wrong rsa block size: %d expecting: %d", host, (loginBlockSize - LOGIN_BLOCK_HEADER_SIZE), expectedSize));
177 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
178 return;
179 }
180
181 final byte[] rsaBytes = new byte[loginBlockSize - LOGIN_BLOCK_HEADER_SIZE];
182 in.readBytes(rsaBytes);
183
184 final byte[] rsaBufBytes = new BigInteger(rsaBytes).modPow(Config.RSA_EXPONENT, Config.RSA_MODULUS).toByteArray();
185 final int rsaBufferSize = rsaBufBytes.length;
186 final ByteBuf rsaBuffer = in.alloc().buffer(rsaBufferSize, rsaBufferSize);
187 try {
188 rsaBuffer.writeBytes(rsaBufBytes);
189
190 final int rsa = rsaBuffer.readUnsignedByte();
191
192 if (rsa != 10) {
193 logger.warn(String.format("[%s] failed decrypt rsa %d", host, rsa));
194 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
195 return;
196 }
197
198 final long clientHalf = rsaBuffer.readLong();
199 final long serverHalf = rsaBuffer.readLong();
200
201 int[] isaacSeed = {
202 (int) (clientHalf >> 32),
203 (int) clientHalf,
204 (int) (serverHalf >> 32),
205 (int) serverHalf
206 };
207
208 final IsaacCipher decryptor = new IsaacCipher(isaacSeed);
209
210 for (int index = 0; index < isaacSeed.length; index++) {
211 isaacSeed[index] += 50;
212 }
213
214 final IsaacCipher encryptor = new IsaacCipher(isaacSeed);
215
216 @SuppressWarnings("unused") final int uid = rsaBuffer.readInt();
217
218 final String UUID = ByteBufUtil.readString(rsaBuffer);
219 final String macAddress = ByteBufUtil.readString(rsaBuffer);
220 final String username = ByteBufUtil.readString(rsaBuffer);
221 final String password = ByteBufUtil.readString(rsaBuffer);
222
223 out.add(new LoginDetailsPacket(UUID, macAddress, username, password, encryptor, decryptor));
224 } finally {
225 rsaBuffer.release();
226 }
227 } else {
228 sendResponseCode(ctx, LoginResponse.LOGIN_SERVER_REJECTED_SESSION);
229 }
230 }
231
232 private void sendResponseCode(ChannelHandlerContext ctx, LoginResponse response) {
233 if (response == LoginResponse.LOGIN_SERVER_REJECTED_SESSION) {
234 final String host = ((InetSocketAddress) ctx.channel().remoteAddress()).getHostString();
235 logger.warn(String.format("[%s] session was rejected", host));
236 }
237 ByteBuf buffer = ctx.alloc().buffer(Byte.BYTES);
238 buffer.writeByte(response.getOpcode());
239 ctx.writeAndFlush(buffer)
240 .addListener(ChannelFutureListener.CLOSE);
241 state = State.IGNORE;
242 }
243
244private enum State {
245 HANDSHAKE,
246 CONNECTION_TYPE,
247 PAYLOAD,
248 IGNORE
249 }
250
251}
static final BigInteger RSA_EXPONENT
Definition Config.java:139
static final AttributeKey< Session > SESSION_KEY
Definition Config.java:131
static final BigInteger RSA_MODULUS
Definition Config.java:136