![]() |
![]() |
|
Blade & Soul Раздел посвящен Blade & Soul - новой игре от NCsoft, которая пока находится в стадии ЗБТ.
Описание темы: ![]() |
![]() |
Опции темы |
![]() |
#1 |
Пользователь
|
![]()
Дорогие, друзья!
К сожалению мой проект не стал опенсурсом. Не стал не потому, что я "зажал" код, а потому что труды мои никто в конце концов не оценит. Да, да, даже ника не вспомните. Поэтому я пилил эмулятор в привате. Сегодня хочу сообщить вам, что разработка эмулятора почти прекращена. Причина очевидная - разработчики изменяют сетевую часть игры каждый вторник, среду. Новые пакеты, новые опкоды. Идти по пути обновлений означает тратить свое время на сбор новых опкодов и разбор структуры пакетов. Не имею желания больше заниматься этим говном. Не потому что игра плохая, игра отличная, но потому что разработчики сами не определились со своей игрой. На этом сопельки можно закончить. Что я планирую сделать в этом топике? Нет, я не выложу вам сурсы и свои наработки, поскольку это означало бы дать вам рыбу, сожрав которую, вы пришли бы в поисках очередной. Я дам вам удачку, ловите сами. Большая часть наработок принадлежит разработчикам Атомикса, ребятам, которые поделились со мной своими наработками. Вопрос о доступности кода мы обсуждали, и они не против, если я покажу вам отрывки кода snippets. Я покажу структуры серверов, покажу опкоды, пакеты, крипты и всякое навроде этого постепенно и шаг за шагом. Вы сможете сами запилить себе эмулятор. Как вы поступите со своими наработками мне не важно. Расшарите или продадите, но начальная информация будет общедоступной на этом форуме. Язык программирования будет C# и все примеры будут включать в себя принципы C#. Но поскольку я не планирую отдать вам рабочий сурс ввиде "просто копипейст энд мейк билд", вы сможете переписать под любой вам нужный язык. Данный топик, пожалуй посвящен людям с опытом в кодинге, нубы и нубские вопросы не приветствуются, т.к. обучать кого-либо я не собираюсь. Нуб? Проходи мимо. Также не приветствуются в топике "Одминистраторы фришек" с вопросами "когда уже выложат рабочий сервер, чтобы на школьниках можно было рубить бабло?". Никто вам ничего не выложит, и если у вас есть деньги но нету мозгов, вам тоже никто не собирается помогать, в частности я. Поэтому не пишите мне в ЛС с предложениями. Ну а теперь перейдем к технической части топика. Начнем естественно с логин сервера. Относительно логин сервера / Concerning LoginServer:Свернуть ↑
Свернуть ↑Развернуть ↓
Пакеты:Свернуть ↑
Свернуть ↑Развернуть ↓
Формируется пакет так: public class BNSLoginPacket : Packet<LoginPacketOpcode> { public string Command { get; set; } public int Serial { get; set; } public byte WorldID { get; set; } public string Content { get; set; } protected XmlDocument ReadLoginPacket() { Serial = GetInt(2); WorldID = GetByte(); Content = Encoding.UTF8.GetString(GetBytes((ushort)(Length - 7), 7)); XmlDocument xml = new XmlDocument(); xml.LoadXml(Content); return xml; } public void WritePacket() { string res = Serial != 0 ? string.Format("{0}\r\ns:{1}R\r\nl:{2}\r\n\r\n{3}", Command, Serial, Encoding.UTF8.GetByteCount(Content), Content) : string.Format("{0}\r\nl:{1}\r\n\r\n{2}", Command, Encoding.UTF8.GetByteCount(Content), Content); byte[] buf = Encoding.UTF8.GetBytes(res); PutBytes(buf, 2); } public override string ToString() { var ser = GetInt(2); var world = GetByte(); var content = Encoding.UTF8.GetString(GetBytes((ushort)(Length - 7), 7)); return string.Format("{0}\n", content); } }ЛогинСервер включает в себя следующие пакеты: CM_STS_CONNECT //просто соединение CM_AUTH_LOGIN_START //запрашивает данные логина CM_AUTH_KEY_DATA //запрашивает обмен ключами CM_AUTH_LOGIN_FINISH //завершает авторизацию CM_AUTH_GAME_TOKEN //передает ключ сессии CM_AUTH_TOKEN //хандшейк между клиентом и логином CM_ACCOUNT_LIST //кол-во аккаунтов CM_WORLD_LIST //кол-во серверов CM_CHAR_LIST //кол-во персонажей CM_CHAR_SLOT_REQUEST //запрашиваем кол-во слотов CM_CHAR_CREATE //создаем персонажа CM_CHAR_DELETE //удаляем персонажа CM_SLOT_LIST //кол-во слотов CM_USER_INFO //данные о персонаже CM_ERROR_RETURN //заглушка для необработанных пакетов CM_PING //просто пинг Есть два типа пакетов. Первый вызывает обработчик, второй формирует данные. CM_ACCOUNT_LIST public CM_ACCOUNT_LIST() { this.ID = LoginPacketOpcode.CM_ACCOUNT_LIST; } public override Packet<LoginPacketOpcode> New() { return new CM_ACCOUNT_LIST(); } public override void OnProcess(Session<LoginPacketOpcode> client) { ((LoginSession)client).OnAccountList(GetInt(2)); }Такие все, за исключением следующих: CM_AUTH_KEY_DATA public CM_AUTH_KEY_DATA() { this.ID = LoginPacketOpcode.CM_AUTH_KEY_DATA; } public override Packet<LoginPacketOpcode> New() { return new CM_AUTH_KEY_DATA(); } public override void OnProcess(Session<LoginPacketOpcode> client) { XmlDocument xml = ReadLoginPacket(); XmlElement root; XmlNodeList list; byte[] keyData = null; root = xml["Request"]; list = root.ChildNodes; foreach (object j in list) { XmlElement i; if (j.GetType() != typeof(XmlElement)) continue; i = (XmlElement)j; switch (i.Name.ToLower()) { case "keydata": keyData = Convert.FromBase64String(i.InnerText); break; } } xml = null; System.IO.MemoryStream ms = new System.IO.MemoryStream(keyData); System.IO.BinaryReader br = new System.IO.BinaryReader(ms); byte[] exchangeKey = br.ReadBytes(br.ReadInt32()); byte[] hash = br.ReadBytes(br.ReadInt32()); string checkHash = Convert.ToBase64String(hash); ((LoginSession)client).OnAuthKeyData(exchangeKey, checkHash, Serial); } }CM_AUTH_LOGIN_START: public CM_AUTH_LOGIN_START() { this.ID = LoginPacketOpcode.CM_AUTH_LOGIN_START; } public override Packet<LoginPacketOpcode> New() { return new CM_AUTH_LOGIN_START(); } public override void OnProcess(Session<LoginPacketOpcode> client) { XmlDocument xml = ReadLoginPacket(); XmlElement root; XmlNodeList list; string loginName = ""; root = xml["Request"]; list = root.ChildNodes; foreach (object j in list) { XmlElement i; if (j.GetType() != typeof(XmlElement)) continue; i = (XmlElement)j; switch (i.Name.ToLower()) { case "loginname": loginName = i.InnerText; break; } } xml = null; ((LoginSession)client).OnAuthLoginStart(loginName, Serial); }CM_CHAR_CREATE: public override void OnProcess(Session<LoginPacketOpcode> client) { XmlDocument xml = ReadLoginPacket(); XmlElement root; XmlNodeList list; byte[] keyData = null; root = xml["bns"]; list = root.ChildNodes; byte slotID = 0; string charName = ""; byte[] charData = null; foreach (object j in list) { XmlElement i; if (j.GetType() != typeof(XmlElement)) continue; i = (XmlElement)j; switch (i.Name.ToLower()) { case "slotid": Guid id = Guid.Parse(i.InnerText); for (slotID = 0; slotID < 5; slotID++) { if (((uint)slotID).ToGUID() == id) break; } break; case "charname": charName = i.InnerText; break; case "chardata": charData = Convert.FromBase64String(i.InnerText); break; } } xml = null; ((LoginSession)client).OnCharCreate(GetInt(2), WorldID, slotID, charName, charData); }CM_CHAR_DELETE: public override void OnProcess(Session<LoginPacketOpcode> client) { XmlDocument xml = ReadLoginPacket(); XmlElement root; XmlNodeList list; root = xml["bns"]; list = root.ChildNodes; byte slotID = 0; foreach (object j in list) { XmlElement i; if (j.GetType() != typeof(XmlElement)) continue; i = (XmlElement)j; switch (i.Name.ToLower()) { case "slotid": Guid id = Guid.Parse(i.InnerText); for (slotID = 0; slotID < 5; slotID++) { if (((uint)slotID).ToGUID() == id) break; } break; } } xml = null; ((LoginSession)client).OnCharDelete(GetInt(2), slotID); }CM_CHAR_SLOT_REQUEST: public override void OnProcess(Session<LoginPacketOpcode> client) { XmlDocument xml = ReadLoginPacket(); XmlElement root; XmlNodeList list; string loginName = ""; root = xml["Request"]; list = root.ChildNodes; byte slotID = 0; foreach (object j in list) { XmlElement i; if (j.GetType() != typeof(XmlElement)) continue; i = (XmlElement)j; switch (i.Name.ToLower()) { case "slotid": Guid id = Guid.Parse(i.InnerText); for (slotID = 0; slotID < 5; slotID++) { if (((uint)slotID).ToGUID() == id) break; } break; } } xml = null; ((LoginSession)client).OnCharSlotRequest(GetInt(2), slotID); } Крипт / crypto:Свернуть ↑
Свернуть ↑Развернуть ↓
Состоит из двух частей: Обмен ключами: public unsafe class BNSKeyExchange : EncryptionKeyExchange { public static BigInteger N = new BigInteger("E306EBC02F1DC69F5B437683FE3851FD9AAA6E97F4CBD42FC06C72053CBCED68EC570E6666F529C58518CF7B299B5582495DB169ADF48ECEB6D65461B4D7C75DD1DA89601D5C498EE48BB950E2D8D5E0E0C692D613483B38D381EA9674DF74D67665259C4C31A29E0B3CFF7587617260E8C58FFA0AF8339CD68DB3ADB90AAFEE"); public static BigInteger P = new BigInteger("7A39FF57BCBFAA521DCE9C7DEFAB520640AC493E1B6024B95A28390E8F05787D"); public static byte[] staticKey = Conversions.HexStr2Bytes("AC34F3070DC0E52302C2E8DA0E3F7B3E63223697555DF54E7122A14DBC99A3E8"); public static BigInteger Two = new BigInteger(2); BigInteger privateKey; BigInteger exchangeKey = Two; BigInteger exchangeKeyServer; SHA256 sha = SHA256.Create(); BigInteger session = new BigInteger((ulong)Global.Random.Next() << 32 | (uint)Global.Random.Next()); byte[] passwordHash, usernameHash; string user, password, authentication; public string Username { get { return user; } set { user = value; } } public string Password { get { return password; } set { password = value; } } public BigInteger Session { get { return session; } set { session = value; } } public override EncryptionKeyExchange CreateNewInstance() { return new BNSKeyExchange(); } public override void MakePrivateKey() { privateKey = new BigInteger(sha.ComputeHash(Encoding.ASCII.GetBytes(DateTime.Now.Ticks.ToString()))); } BigInteger GetKeyExchange() { if (exchangeKey == Two) exchangeKey = Two.modPow(privateKey, N); return exchangeKey; } public BigInteger GetKeyExchangeClient() { return GetKeyExchange(); } BigInteger GetKeyExchangeServer() { if (exchangeKey == Two) { try { exchangeKey = Two.modPow(privateKey, N); string username = user + "@plaync.co.kr"; passwordHash = sha.ComputeHash(Encoding.ASCII.GetBytes(username + ":" + password)); usernameHash = sha.ComputeHash(Encoding.ASCII.GetBytes(username)); BigInteger hash2 = SHA256Hash2ArrayInverse(session.getBytes(), passwordHash); BigInteger v25 = Two.modPow(hash2, N); v25 = (v25 * P) % N; exchangeKeyServer = (exchangeKey + v25) % N; } catch { } } return exchangeKeyServer; } public override byte[] GetKeyExchangeBytes(Mode mode) { switch (mode) { case Mode.Client: return GetKeyExchangeClient().getBytes(); case Mode.Server: return GetKeyExchangeServer().getBytes(); } return null; } public byte[][] GenerateKeyClient(BigInteger exchangeKey) { string username = user + "@plaync.co.kr"; byte[] passwordHash = sha.ComputeHash(Encoding.ASCII.GetBytes(username + ":" + password)); BigInteger hash1 = SHA256Hash2ArrayInverse(this.GetKeyExchange().getBytes(), exchangeKey.getBytes()); BigInteger hash2 = SHA256Hash2ArrayInverse(session.getBytes(), passwordHash); BigInteger v27 = new BigInteger(exchangeKey.getBytes()); BigInteger v25 = Two.modPow(hash2, N); v25 = (v25 * P) % N; while (v27 < v25) v27 += N; v27 -= v25; BigInteger v24 = ((hash1 * hash2) + privateKey) % N; BigInteger v21 = v27.modPow(v24, N); key = GenerateEncryptionKeyRoot(v21.getBytes()); byte[] chash1 = sha.ComputeHash(CombineBuffers(staticKey, sha.ComputeHash(Encoding.ASCII.GetBytes(username)), session.getBytes(), this.GetKeyExchange().getBytes(), exchangeKey.getBytes(), key)); byte[] chash2 = sha.ComputeHash(CombineBuffers(this.GetKeyExchange().getBytes(), chash1, key)); key = Generate256BytesKey(key); return new byte[][] { chash1, chash2 }; } public byte[][] GenerateKeyServer(BigInteger exchangeKey) { BigInteger hash1 = SHA256Hash2ArrayInverse(exchangeKey.getBytes(), this.GetKeyExchangeServer().getBytes()); BigInteger hash2 = SHA256Hash2ArrayInverse(session.getBytes(), passwordHash); BigInteger v27 = new BigInteger(exchangeKey.getBytes()); BigInteger v21 = (this.GetKeyExchange().modPow((hash1 * hash2), N) * v27.modPow(privateKey, N)) % N; key = GenerateEncryptionKeyRoot(v21.getBytes()); byte[] chash1 = sha.ComputeHash(CombineBuffers(staticKey, usernameHash, session.getBytes(), exchangeKey.getBytes(), GetKeyExchangeServer().getBytes(), key)); byte[] chash2 = sha.ComputeHash(CombineBuffers(exchangeKey.getBytes(), chash1, key)); key = Generate256BytesKey(key); return new byte[][] { chash1, chash2 }; } public override void MakeKey(Mode mode, byte[] keyExchangeBytes) { byte[][] checkHash = null; BigInteger exchange = new BigInteger(keyExchangeBytes); switch (mode) { case Mode.Client: checkHash = GenerateKeyClient(exchange); break; case Mode.Server: checkHash = GenerateKeyServer(exchange); break; } authentication = Convert.ToBase64String(checkHash[0]) + "," + Convert.ToBase64String(checkHash[1]); } public override bool IsReady { get { return key != null; } } BigInteger SHA256Hash2ArrayInverse(byte[] tmp1, byte[] tmp2) { BigInteger hash; byte[] combine = new byte[tmp1.Length + tmp2.Length]; tmp1.CopyTo(combine, 0); tmp2.CopyTo(combine, tmp1.Length); byte[] buf = sha.ComputeHash(combine); byte[] res = IntegerReverse(buf); hash = new BigInteger(res); return hash; } byte[] IntegerReverse(byte[] buf) { byte[] res = new byte[buf.Length]; for (int i = 0; i < res.Length / 4; i++) { fixed (byte* ptr = buf) { fixed (byte* ptr2 = res) { int* src = (int*)ptr; int* dst = (int*)ptr2; dst[i] = src[res.Length / 4 - 1 - i]; } } } return res; } byte[] GenerateEncryptionKeyRoot(byte[] src) { int firstSize = src.Length; int startIndex = 0; byte[] half; byte[] dst = new byte[64]; if (src.Length > 4) { do { if (src[startIndex] == 0) break; firstSize--; startIndex++; } while (firstSize > 4); } int size = firstSize >> 1; half = new byte[size]; if (size > 0) { int index = startIndex + firstSize - 1; for (int i = 0; i < size; i++) { half[i] = src[index]; index -= 2; } } byte[] hash = sha.ComputeHash(half, 0, size); for (int i = 0; i < 32; i++) { dst[2 * i] = hash[i]; } if (size > 0) { int index = startIndex + firstSize - 2; for (int i = 0; i < size; i++) { half[i] = src[index]; index -= 2; } } hash = sha.ComputeHash(half, 0, size); for (int i = 0; i < 32; i++) { dst[2 * i + 1] = hash[i]; } return dst; } byte[] CombineBuffers(params byte[][] buffers) { int len = 0; foreach (byte[] i in buffers) { len += i.Length; } byte[] res = new byte[len]; int index = 0; foreach (byte[] i in buffers) { i.CopyTo(res, index); index += i.Length; } return res; } byte[] Generate256BytesKey(byte[] src) { int v7 = 1; byte[] res = new byte[256]; for (int i = 0; i < 256; i++) res[i] = (byte)i; int v6 = 0; int counter = 0; for (int i = 64; i > 0; i--) { int v9 = (v6 + src[counter] + res[v7 - 1]) & 0xFF; int v10 = res[v7 - 1]; res[v7 - 1] = res[v9]; int v8 = counter + 1; res[v9] = (byte)v10; if (v8 == src.Length) v8 = 0; int v13 = v9 + src[v8]; int v11 = v8 + 1; int v14 = v13 + res[v7]; v13 = res[v7]; int v12 = (byte)v14; res[v7] = res[v12]; res[v12] = (byte)v13; if (v11 == src.Length) v11 = 0; int v16 = (v12 + src[v11] + res[v7 + 1]) & 0xFF; int v17 = res[v7 + 1]; res[v7 + 1] = res[v16]; int v15 = v11 + 1; res[v16] = (byte)v17; if (v15 == src.Length) v15 = 0; int v18 = v16 + src[v15]; int v19 = res[v7 + 2]; v6 = (v18 + res[v7 + 2]) & 0xFF; counter = v15 + 1; res[v7 + 2] = res[v6]; res[v6] = (byte)v19; if (counter == src.Length) counter = 0; v7 += 4; } return res; } }Ответный ключ: public abstract class EncryptionKeyExchange { public static BigInteger Module = new BigInteger(Conversions.HexStr2Bytes("f488fd584e49dbcd20b49de49107366b336c380d451d0f7c88b31c7c5b2d8ef6f3c923c043f0a55b188d8ebb558cb85d38d334fd7c175743a31d186cde33212cb52aff3ce1b1294018118d7c84a70a72d686c40319c807297aca950cd9969fabd00a509b0246d3083d66a45d419f9c7cbd894b221926baaba25ec355e92f78c7")); protected byte[] key; public byte[] Key { get { return key; } } public abstract EncryptionKeyExchange CreateNewInstance(); public abstract void MakePrivateKey(); public abstract byte[] GetKeyExchangeBytes(Mode mode); public abstract void MakeKey(Mode mode, byte[] keyExchangeBytes); public abstract bool IsReady { get; } } Обработчики:Свернуть ↑
Свернуть ↑Развернуть ↓
При входе: public partial class LoginSession : Session<LoginPacketOpcode> { int loginSerial; string checkHash; Account account; AccountLoginResult lastLoginRes = AccountLoginResult.INVALID_PASSWORD; public Account Account { get { return account; } } public void OnAuthLoginStart(string loginName, int serial) { loginSerial = serial; AccountSession.Instance.RequestAccountInfo(loginName.Split('@')[0], this); Logger.ShowInfo(loginName + " попытка соединения"); } public void OnAccountInfo(AccountLoginResult res, Account account) { if (res == AccountLoginResult.NO_SUCH_ACCOUNT) Logger.ShowInfo("Результат соединения:" + res.ToString()); switch (res) { case AccountLoginResult.OK: { this.account = account; this.Network.Crypt.KeyExchange.MakePrivateKey(); ((BNSKeyExchange)this.Network.Crypt.KeyExchange).Username = account.UserName; ((BNSKeyExchange)this.Network.Crypt.KeyExchange).Password = account.Password; byte[] exchange = this.Network.Crypt.KeyExchange.GetKeyExchangeBytes(Mode.Server); byte[] session = ((BNSKeyExchange)this.Network.Crypt.KeyExchange).Session.getBytes(); System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms); bw.Write(session.Length); bw.Write(session); bw.Write(exchange.Length); bw.Write(exchange); string key = string.Format("<Reply>\n<KeyData>{0}</KeyData>\n</Reply>\n", Convert.ToBase64String(ms.ToArray())); BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = loginSerial; p.Content = key; p.WritePacket(); this.Network.SendPacket(p); } break; case AccountLoginResult.NO_SUCH_ACCOUNT: case AccountLoginResult.DB_ERROR: { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 400 ErrAccountNotFound"; p.Serial = loginSerial; p.Content = "<Error code=\"3002\" server=\"1008\" module=\"1\" line=\"458\"/>\n"; p.WritePacket(); this.Network.SendPacket(p); } break; } } public void OnAuthKeyData(byte[] exchangeKey, string checkHash, int serial) { this.Network.Crypt.KeyExchange.MakeKey(Mode.Server, exchangeKey); string hash = ((BNSKeyExchange)this.Network.Crypt.KeyExchange).Authentication; if (checkHash == hash.Split(',')[0]) { this.checkHash = hash.Split(',')[1]; loginSerial = serial; AccountSession.Instance.AccountLogin(account.AccountID, this); } else { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 400 ErrBadPasswd"; p.Serial = serial; p.Content = "<Error code=\"11\" server=\"1012\" module=\"1\" line=\"1683\"/>\n"; p.Encrypt = false; p.WritePacket(); this.Network.SendPacket(p); Logger.ShowInfo("Login result for " + account.UserName + ": BadPassword"); } } public void OnAccountLoginResult(AccountLoginResult result) { lastLoginRes = result; if (result == AccountLoginResult.OK) { account.LastLoginTime = DateTime.Now; account.LastLoginIP = Network.Socket.RemoteEndPoint.ToString().Split(':')[0]; System.IO.MemoryStream ms = new System.IO.MemoryStream(); System.IO.BinaryWriter bw = new System.IO.BinaryWriter(ms); bw.Write(32); bw.Write(Convert.FromBase64String(checkHash)); string res = Convert.ToBase64String(ms.ToArray()); string key = string.Format("<Reply>\n<KeyData>{0}</KeyData>\n</Reply>\n", res); BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = loginSerial; p.Content = key; p.Encrypt = false; p.WritePacket(); this.Network.SendPacket(p); Logger.ShowInfo(account.UserName + " login successful!"); } else { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 400 ErrBadPasswd";//should be already log in p.Serial = loginSerial; p.Content = "<Error code=\"11\" server=\"1012\" module=\"1\" line=\"1683\"/>\n"; p.Encrypt = false; p.WritePacket(); this.Network.SendPacket(p); Logger.ShowInfo("Login result for " + account.UserName + ": ALREADY_LOG_IN"); AccountSession.Instance.AccountLogout(account.AccountID, this); } } public void OnAuthLoginFinish(int serial) { Guid accountGuid = account.AccountID.ToGUID(); BNSLoginPacket p = new BNSLoginPacket(); p.Command = "POST /Presence/UserInfo STS/1.0"; p.Serial = 0; p.Content = string.Format("<Message>\n<UserId>{0}</UserId>\n<UserCenter>1</UserCenter>\n<UserName>{1}</UserName>\n<Status>online</Status>\n<Aliases type=\"array\">\n<Alias>{1}</Alias>\n<Alias>bns:{1}</Alias>\n</Aliases>\n</Message>\n", accountGuid.ToString().ToUpper(), account.UserName); p.WritePacket(); this.Network.SendPacket(p); p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; p.Content = string.Format("<Reply>\n<UserId>{0}</UserId>\n<UserCenter>1</UserCenter>\n<Roles type=\"array\"/>\n<LocationId>32B0E246-1D5F-4AC1-AC30-E462AAF4C870</LocationId>\n<AccessMask>1073741823</AccessMask>\n<UserName>{1}</UserName>\n</Reply>\n", accountGuid.ToString().ToUpper(), account.UserName); Logger.ShowInfo("account:" + accountGuid.ToString()); p.WritePacket(); this.Network.SendPacket(p); } public void OnUserInfo(int serial) { Guid accountGuid = account.AccountID.ToGUID(); BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; p.Content = string.Format("<Reply>\n<UserId>{0}</UserId>\n<UserCenter>1</UserCenter>\n<UserName>{1}</UserName>\n<LoginName>{1}@plaync.co.kr</LoginName>\n<UserStatus>1</UserStatus>\n</Reply>\n", accountGuid.ToString().ToUpper(), account.UserName); p.WritePacket(); this.Network.SendPacket(p); } public void OnAuthGameToken(int serial) { account.LoginToken = ((uint)Global.Random.Next()).ToGUID(); Logger.ShowInfo("gametoken:" + account.LoginToken.ToString().ToUpper()); account.TokenExpireTime = DateTime.Now.AddMinutes(10); AccountSession.Instance.AccountSave(account, this); BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; p.Content = string.Format("<Reply>\n<Token>{0}</Token>\n</Reply>\n", account.LoginToken.ToString().ToUpper()); p.WritePacket(); this.Network.SendPacket(p); } public void OnAuthToken(int serial) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; string token = account.AccountID.ToGUID().ToString().ToUpper() + ":" + ((uint)Global.Random.Next()).ToGUID().ToString().ToUpper(); Logger.ShowInfo("Auth Token: " + token); p.Content = string.Format("<Reply>\n<AuthnToken>{0}</AuthnToken>\n</Reply>\n", Convert.ToBase64String(Encoding.Default.GetBytes(token))); p.WritePacket(); this.Network.SendPacket(p); } public void OnError(int serial) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = serial == 8 ? "STS/1.0 400 ErrSecondPasswordNotFound" : "STS/1.0 400 ErrPermission"; p.Serial = serial; //<Error code="3333" server="107" module="2" line="811"/> p.Content = serial == 8 ? "<Error code=\"3333\" server=\"107\" module=\"2\" line=\"811\"/>\n" : "<Error code=\"49\" server=\"1001\" module=\"8000\" line=\"1086\"/>\n"; p.WritePacket(); this.Network.SendPacket(p); } public void OnAccountList(int serial) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; p.Content = string.Format("<Reply type=\"array\">\n<GameAccount>\n<Alias>{0}</Alias>\n<Created>2013-06-08T17:45:58.689+09:00</Created>\n</GameAccount>\n</Reply>\n", account.AccountID.ToGUID().ToString().ToUpper()); p.WritePacket(); this.Network.SendPacket(p); } public void OnWorldList(int serial) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; string worlds = ""; int idx = 1; foreach (WorldInfo i in Configuration.Instance.Worlds) { worlds += string.Format("<World>\n<WorldCode>{0}</WorldCode>\n<WorldName>{1}</WorldName>\n<PublicNetAddress>{2}</PublicNetAddress>\n<Property>\n<EnableCharacterCreate>true</EnableCharacterCreate>\n<WorldScore>0</WorldScore>\n</Property>\n<UserCounts>\n<PlayingUsers>426</PlayingUsers>\n<WaitingUsers>0</WaitingUsers>\n<MaxUsers>4000</MaxUsers>\n</UserCounts>\n</World>\n", idx++, i.Name, i.Address); } p.Content = string.Format("<Reply type=\"array\">\n{0}\n</Reply>\n", worlds); p.WritePacket(); this.Network.SendPacket(p); } }При чтении данных персонажа: public partial class LoginSession : Session<LoginPacketOpcode> { int characterSerial = 0, removeSerial = 0; Dictionary<byte, ActorPC> chars; int currentCharIndex; ActorPC createdPC, removePC; public void OnCharList(int serial) { characterSerial = serial; CharacterSession.Instance.RequestCharList(account.AccountID, this); } public void OnGotCharList(List<ActorPC> chars) { this.chars=new Dictionary<byte,ActorPC>(); foreach (ActorPC i in chars) this.chars[i.SlotID] = i; currentCharIndex = 0; LoadInventory(); } public void SendSlotList(int serial) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; string content = "<Reply type=\"array\">\n"; for (byte i = 0; i < (3 + this.account.ExtraSlots); i++) { content += string.Format("<Slot>\n<SlotId>{0}</SlotId>\n<AppGroupId>2</AppGroupId>\n<SlotType>char</SlotType>\n<SystemSlot>1</SystemSlot>\n<Changed>2012-04-25T05:52:22Z</Changed>\n<Registered>2012-04-25T05:52:22Z</Registered>\n</Slot>\n", ((uint)i).ToGUID().ToString().ToUpper()); } p.Content = content + "</Reply>\n"; p.WritePacket(); this.Network.SendPacket(p); } public void SendCharList() { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = characterSerial; string content = "<Reply>\n"; for (byte i = 0; i < 5; i++) { if (chars.ContainsKey(i)) { ActorPC pc = chars[i]; System.IO.MemoryStream ms = new System.IO.MemoryStream(); pc.AppearenceToSteam(ms); content += string.Format("<CharSlot>\n<SlotId>{0}</SlotId>\n<CharId>{1}</CharId>\n<WorldCode>{4}</WorldCode>\n<CharName>{2}</CharName>\n<CharData><bns><pcdbid>{1}</pcdbid><showcase>{3}</showcase></bns></CharData>\n</CharSlot>\n", ((uint)pc.SlotID).ToGUID(), pc.CharID, pc.Name, Convert.ToBase64String(ms.ToArray()), pc.WorldID); ms.Close(); ms = null; } else content += string.Format("<CharSlot><SlotId>{0}</SlotId>\n</CharSlot>\n", ((uint)i).ToGUID()); } p.Content = content + "</Reply>\n"; p.WritePacket(); this.Network.SendPacket(p); } public void OnCharSlotRequest(int serial, byte slotID) { if (chars.ContainsKey(slotID)) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = serial; string content = "<Reply>\n"; ActorPC pc = chars[slotID]; System.IO.MemoryStream ms = new System.IO.MemoryStream(); pc.AppearenceToSteam(ms); content += string.Format("<SlotId>{0}</SlotId>\n<CharId>{1}</CharId>\n<WorldCode>{4}</WorldCode>\n<CharName>{2}</CharName>\n<CharData><bns><pcdbid>{1}</pcdbid><showcase>{3}</showcase></bns></CharData>\n", ((uint)pc.SlotID).ToGUID(), pc.CharID, pc.Name, Convert.ToBase64String(ms.ToArray()), pc.WorldID); ms.Close(); ms = null; p.Content = content + "</Reply>\n"; p.WritePacket(); this.Network.SendPacket(p); } } public void OnCharDelete(int serial, byte slotID) { if (chars.ContainsKey(slotID)) { removePC = chars[slotID]; removeSerial = serial; CharacterSession.Instance.DeleteChar(removePC.CharID, this); } } public void OnCharDeleteResult(SM_CHAR_DELETE_RESULT.Results Result) { if (Result == SM_CHAR_DELETE_RESULT.Results.OK && removePC != null) { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = removeSerial; p.Content = string.Format("<bns>\n<protocol>Game.bns.{1}</protocol>\n<command>DeletePc</command>\n<result>OK</result>\n<slotid>{0}</slotid>\n</bns>\n", ((uint)removePC.SlotID).ToGUID(), removePC.WorldID); p.WritePacket(); this.Network.SendPacket(p); if (chars.ContainsKey(removePC.SlotID)) chars.Remove(removePC.SlotID); } else { removePC = null; BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = removeSerial; p.Content = string.Format("<bns>\n<protocol>Game.bns.{1}</protocol>\n<command>DeletePc</command>\n<result>Fail</result>\n<slotid>{0}</slotid>\n<reason>202</reason></bns>\n", ((uint)removePC.SlotID).ToGUID(), removePC.WorldID); p.WritePacket(); this.Network.SendPacket(p); } } public void OnCharCreate(int serial, byte worldID, byte slotID, string charName, byte[] charData) { System.IO.MemoryStream ms = new System.IO.MemoryStream(charData); System.IO.BinaryReader br = new System.IO.BinaryReader(ms); ms.Position = 2; byte[] display1 = Conversions.HexStr2Bytes("6363634E63737364777777636464776464646464646464"); br.ReadBytes(br.ReadInt16()).CopyTo(display1, 0); ActorPC pc = new ActorPC(); pc.Level = 1; pc.AccountID = account.AccountID; pc.Name = charName; pc.SlotID = slotID; pc.WorldID = worldID; pc.Appearence1 = display1; ms.Position += 2; pc.Race = (Race)br.ReadByte(); ms.Position += 2; pc.Gender = (Gender)br.ReadByte(); ms.Position += 2; pc.Job = (Job)br.ReadByte(); ms.Position += 2; pc.Appearence2 = br.ReadBytes(br.ReadInt16()); pc.MapID = 1101; pc.X = -3177; pc.Y = 9243; pc.Z = 599; pc.Dir = 45; pc.UISettings = ""; pc.InventorySize = 32; switch (pc.Job) { case Job.Assassin: pc.HP = 66; pc.MP = 0; pc.MaxHP = 66; pc.MaxMP = 10; break; case Job.ForceMaster: pc.HP = 61; pc.MP = 10; pc.MaxHP = 61; pc.MaxMP = 10; break; case Job.KungfuMaster: pc.HP = 109; pc.MaxHP = 109; pc.MP = 0; pc.MaxMP = 10; break; case Job.BladeMaster: default: pc.HP = 99; pc.MP = 0; pc.MaxHP = 99; pc.MaxMP = 10; break; } characterSerial = serial; createdPC = pc; CharacterSession.Instance.CreateChar(pc, this); } public void OnCharCreateResult(uint charID,SM_CHAR_CREATE_RESULT.Results Result) { if (Result == SM_CHAR_CREATE_RESULT.Results.OK) { createdPC.CharID = charID; Base.Quests.Quest q = new Base.Quests.Quest(); q.QuestID = 250; q.Step = 1; q.NextStep = 1; createdPC.Quests[q.QuestID] = q; CharacterSession.Instance.CharacterSave(createdPC); BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = characterSerial; p.Content = string.Format("<bns>\n<protocol>Game.bns.{1}</protocol>\n<command>CreatePc</command>\n<result>OK</result>\n<slotid>{0}</slotid>\n</bns>\n", ((uint)createdPC.SlotID).ToGUID(), createdPC.WorldID); p.WritePacket(); this.Network.SendPacket(p); if (!chars.ContainsKey(createdPC.SlotID)) chars[createdPC.SlotID] = createdPC; } else { BNSLoginPacket p = new BNSLoginPacket(); p.Command = "STS/1.0 200 OK"; p.Serial = characterSerial; p.Content = string.Format("<bns>\n<protocol>Game.bns.{1}</protocol>\n<command>CreatePc</command>\n<result>Fail</result>\n<slotid>{0}</slotid>\n<reason>202</reason>\n</bns>\n", ((uint)createdPC.SlotID).ToGUID(), createdPC.WorldID); p.WritePacket(); this.Network.SendPacket(p); } } } Сеть:Свернуть ↑
Свернуть ↑Развернуть ↓
public class BNSLoginNetwork<T> : Network<T> { new string lastContent = ""; public override Network<T> CreateNewInstance(System.Net.Sockets.Socket sock, Dictionary<T, Packet<T>> commandTable, Session<T> client) { BNSLoginNetwork<T> instance = new BNSLoginNetwork<T>(); CreateNewInstance(instance, sock, commandTable, client); return instance; } protected override void OnReceivePacket(byte[] buffer) { if (Crypt.KeyExchange.IsReady) Crypt.Decrypt(buffer, 0, buffer.Length); string content = lastContent + Encoding.UTF8.GetString(buffer); try { ParseContent(content); } catch (Exception ex) { Logger.ShowError(ex); } } void ParseContent(string content) { System.IO.StringReader sr = new System.IO.StringReader(content); string header = sr.ReadLine(); int length = int.Parse(sr.ReadLine().Split(':')[1]); string tmp = sr.ReadLine(); string serial=""; if (tmp != "") { serial = tmp; do { tmp = sr.ReadLine(); } while (tmp != "" && sr.Peek() != -1); } byte worldID; LoginPacketOpcode opcode = ParseHeader(header, out worldID); if (opcode == LoginPacketOpcode.Unknown) { Logger.ShowError("Неизвестный пакет:"); Logger.ShowError(content); } byte[] buf = Encoding.UTF8.GetBytes(sr.ReadToEnd()); if (buf.Length >= length) { byte[] tmp2 = new byte[length]; Array.Copy(buf, 0, tmp2, 0, length); Packet<T> p = new Packet<T>(); p.ID = (T)(object)(int)opcode; p.PutInt(serial != "" ? int.Parse(serial.Split(':')[1]) : 0, 2); p.PutByte(worldID); p.PutBytes(tmp2, 7); ProcessPacket(p); if (length < buf.Length) ParseContent(Encoding.UTF8.GetString(buf, length, buf.Length - length)); else lastContent = ""; } else lastContent = content; } LoginPacketOpcode ParseHeader(string header ,out byte worldID) { string[] token = header.Split(' '); return GetOpcode(token[1], out worldID); } public static LoginPacketOpcode GetOpcode(string header,out byte worldID) { worldID = 1; if (header.Contains("/Game.bns.")) { string[] token = header.Split('/'); worldID = byte.Parse(token[1].Substring(9)); token[1] = token[1].Substring(0, 8); header = "/" + token[1] + "/" + token[2]; } switch (header) { case "/Sts/Connect": return LoginPacketOpcode.CM_STS_CONNECT; case "/Auth/LoginStart": return LoginPacketOpcode.CM_AUTH_LOGIN_START; case "/Auth/KeyData": return LoginPacketOpcode.CM_AUTH_KEY_DATA; case "/Auth/LoginFinish": return LoginPacketOpcode.CM_AUTH_LOGIN_FINISH; case "/Auth/RequestToken": return LoginPacketOpcode.CM_AUTH_TOKEN; case "/Auth/RequestGameToken": return LoginPacketOpcode.CM_AUTH_GAME_TOKEN; case "/GameAccount/ListMyAccounts": return LoginPacketOpcode.CM_ACCOUNT_LIST; case "/World/ListWorlds": return LoginPacketOpcode.CM_WORLD_LIST; case "/Slot/ListCharSlots": return LoginPacketOpcode.CM_CHAR_LIST; case "/Slot/GetCharSlot": return LoginPacketOpcode.CM_CHAR_SLOT_REQUEST; case "/Game.bns/CreatePC": return LoginPacketOpcode.CM_CHAR_CREATE; case "/Game.bns/DeletePC": return LoginPacketOpcode.CM_CHAR_DELETE; case "/Sts/Ping": return LoginPacketOpcode.CM_PING; case "/Slot/ListSlots": return LoginPacketOpcode.CM_SLOT_LIST; case "/Auth/GetMyUserInfo": return LoginPacketOpcode.CM_USER_INFO; case "/SecondPassword/GetStatus": case "/Grade.bns/GetGameGrade": case "/Friend/GetUserInfo": case "/VirtualCurrency/GetBalance": case "/Friend/PageRecvProposals": case "/Goods/ListCategoryIds": return LoginPacketOpcode.CM_ERROR_RETURN; } return LoginPacketOpcode.Unknown; } public override void SendPacket(Packet<T> p, bool noWarper) { throw new NotImplementedException(); } public override void SendPacket(Packet<T> p) { byte[] buf = p.GetBytes((ushort)(p.Length - 2), 2); if (Crypt.KeyExchange.IsReady && p.Encrypt) Crypt.Encrypt(buf, 0, buf.Length); SendPacketRaw(buf, 0, buf.Length); } } Этой информации достаточно, чтобы написать рабочий логин сервер. Да, вы не нашли здесь систем создания аккаунтов, соединение с БД и т.д. Я намеренно не буду выкладывать такие вещи, поскольку каждый кто будет писать свой эмулятор, напишет эти модули под себя и под свою выбранную БД. Продолжение следует. Топик буду обновлять по мере ваших успехов и продвижений в разработке. Последний раз редактировалось luna9966; 13.03.2014 в 19:02. |
![]() |
![]() |
Сказали спасибо: |
![]() |
#2 |
![]() |
![]()
Автора знаю прилично и сталкивались с разными проблемами в работе, которые были успешно исправлены. Конечно по поводу архитектуры у всех свои фломастеры , но уверен что будет успех
|
![]() |
![]() |
![]() |
#3 |
Antihero
Регистрация: 03.04.2010
Адрес: Virtual Reality
Сообщений: 2,455
Отблагодарили 1,098 раз(а)
Рейтинг мнений:
885
|
![]() offtop:Свернуть ↑
Свернуть ↑Развернуть ↓
Суть опенсорца не в том, что исходный код открыт, а в тех. поддержке. Нуб, не можешь, не умеешь? - Плати опенсорц команде, чтобы они тебе скомпилировали, настроили и запустили. Именно на этом варят деньги огромные опенсорц проекты - на тех. поддержке, без нее не было бы профита, и тот же самый JBOSS давно бы умер. Вам достаточно было повысить уровень сложности для поднятия эмуля и был бы профит. |
![]() |
![]() |
![]() |
#4 |
Пользователь
|
![]()
Хотя интерес к данной теме угас значительно, все-таки решил продолжить ее наполнение. Перед тем, как преступить к изложению концепции системы крипта, который мы использовали, хочу выразить свое негодование по поводу идиотов, которые критикуют код и пишут болдом в каждой теме. Ни о каком уважении речь идти не может. О таких гуру кодертсва могу сказать одно - конченные.
Для отсальных скажу лишь то, что код атомикса выдерживал онлайн в 3к человек и выдерживал его стабильно. Поэтому если хотите убедить меня в том, что наш код плохой, напишите свой сервер без наших наработок и протестируйте его на живом онлайне. Относительно Лобби Сервера:Свернуть ↑
Свернуть ↑Развернуть ↓
На данном этапе я препологаю, что у вас уже имеется рабочая система БД и аккаунтов, а также рабочий логин сервер. Крипт 2:Свернуть ↑
Свернуть ↑Развернуть ↓
Сетевой крипт для лобби и гейм сервера представляет собой систему RSA с публичным и приватным ключем. Кто-то говорит что нет, кто-то говорит что да. Но единного мнения нету. Лично я считаю, что это все-таки RSA который сверху закодированный AES. Спорить не буду, поскольку система рабочая. Для того чтобы расшифровать пакеты лобби сервера, нам понадобится пара ключей, хэндшейк и AES ключ, который генерируется вместе с остальными ключами, которые идут зашифрованные в пакете. Проще говоря нам нужны 2 пакета и 1 AES ключ от конкретной сессии. Каждый АES ключ привязан только к 1 сессии, т.е. после релогина вы не сможете зайти повторно, придется перезагружать игру, чтобы открыть первую сессию. Данные пакеты передаются первыми, так мы узнаем о том, что они несут в себе обмен ключами. Пакеты-ключи:Свернуть ↑
Свернуть ↑Развернуть ↓
От сервера: 404091DDC3566EFFB22973837A0E135FBA3EE5DC67E0850D7AFA8CA743F3764DA2EA428B338A24B11855EDC2AE7E72C3FEA4BECBFE905B537EFE38430B7C68C43F697AC23A7996E16D5D4766BCC352B86D733218DA0604B598EC14932A986AAC910A2FB31F595CD7CD4AB43CFF5CF1C9262760F57DAB453EC816CB366BD0D30FB81F7E05650AF15CE7BFE341BA7D91F4C4F3D3754C937CCF118DEE60F8260AA3B4AEB101BDD1772EC8953CFCCFB150651A9198D0DB2CACA09279CABD18518DAA1E42B429168D4CE9848FE294BF3FF28D35E09CE6DC914613A6F61AB6F4541E6F42FC2D165492879424FED57E2B69BC9854DF2AB2F46A28D6E0A0495E352100F7B72DОт клиента: 10500CC457B8CBBEA528768F06CCED65A1123C100E8D9529829540C186D7084CF2475D9E3B386E8AE8ADD23E0534170685FDEA62095A29F2F6B7C9C9A7E9A9BD8974AES ключ: 09CE3091EC409BAE5649A336C43CBA74Этот метод называется подменой ключей. Истиного RSA у нас никогда не было. Если вам, гуру и джежаем этот метод не нравится - реверсите клиент до той степени, пока не получите полноценный крипт. Я уделил этому много времени, увы и ах. Механизм дешифрования траффика:Свернуть ↑
Свернуть ↑Развернуть ↓
Следующий код, демонстрирует процесс подмены ключей: Ключи можно хранить в XML документе в конфигах. <!-- Настройка AES ключа для шифрования траффика --> <EncryptionKeyPair handshake="404091DDC3566EFFB22973837A0E135FBA3EE5DC67E0850D7AFA8CA743F3764DA2EA428B338A24B11855EDC2AE7E72C3FEA4BECBFE905B537EFE38430B7C68C43F697AC23A7996E16D5D4766BCC352B86D733218DA0604B598EC14932A986AAC910A2FB31F595CD7CD4AB43CFF5CF1C9262760F57DAB453EC816CB366BD0D30FB81F7E05650AF15CE7BFE341BA7D91F4C4F3D3754C937CCF118DEE60F8260AA3B4AEB101BDD1772EC8953CFCCFB150651A9198D0DB2CACA09279CABD18518DAA1E42B429168D4CE9848FE294BF3FF28D35E09CE6DC914613A6F61AB6F4541E6F42FC2D165492879424FED57E2B69BC9854DF2AB2F46A28D6E0A0495E352100F7B72D"> <Key exchangekey="10500CC457B8CBBEA528768F06CCED65A1123C100E8D9529829540C186D7084CF2475D9E3B386E8AE8ADD23E0534170685FDEA62095A29F2F6B7C9C9A7E9A9BD8974"> 09CE3091EC409BAE5649A336C43CBA74 </Key> </EncryptionKeyPair> case "encryptionkeypair": { string handshake = i.Attributes[0].Value.Substring(260); XmlNodeList children = i.ChildNodes; foreach (object l in children) { XmlElement k = l as XmlElement; if (k == null) continue; switch (k.Name.ToLower()) { case "key": byte[][] keypair = new byte[2][]; keypair[0] = Conversions.HexStr2Bytes(k.Attributes[0].Value); keypair[1] = Conversions.HexStr2Bytes(k.InnerText.Replace("\r","").Replace("\n","").Replace(" ","")); Base.BNSGameNetwork<Base.Packets.LobbyPacketOpcode>.AddKeyPair(handshake, keypair); break; } } }Далее переходим к процедуре AddKeyPair и создания сокета для получения и расшифровки пакетов: public class BNSGameNetwork<T> : Network<T> { bool ready = false; static Dictionary<string, List<byte[][]>> ExchangeKeys =new Dictionary<string,List<byte[][]>>(); public static void AddKeyPair(string handshake, byte[][] keypair) { if (!ExchangeKeys.ContainsKey(handshake)) ExchangeKeys.Add(handshake, new List<byte[][]>()); ExchangeKeys[handshake].Add(keypair); } public byte[] ExchangeKey; public override Network<T> CreateNewInstance(System.Net.Sockets.Socket sock, Dictionary<T, Packet<T>> commandTable, Session<T> client) { BNSGameNetwork<T> instance = new BNSGameNetwork<T>(); CreateNewInstance(instance, sock, commandTable, client); return instance; } protected override void OnReceivePacket(byte[] buf) { if (ready) { int totalLen = BitConverter.ToInt16(buf, 0); totalLen = (totalLen & 0xfff) * 4; if (buf.Length - 2 >= totalLen) { try { byte[] buf2 = new byte[totalLen]; Array.Copy(buf, 2, buf2, 0, totalLen); Crypt.Decrypt(buf2, 0, totalLen); int len = BitConverter.ToInt16(buf2, 0) - 2; byte[] tmp = new byte[len]; Array.Copy(buf2, 2, tmp, 0, len); Packet<T> p = new Packet<T>(); p.PutBytes(tmp, 0); ProcessPacket(p); if (totalLen < buf.Length - 2) { int rest = buf.Length - 2 - totalLen; buf2 = new byte[rest]; Array.Copy(buf, totalLen + 2, buf2, 0, rest); OnReceivePacket(buf2); } else lastContent = null; } catch (Exception) { } } else lastContent = buf; } else { string handshake = Conversions.bytes2HexString(buf); List<byte[][]> list; if (ExchangeKeys.TryGetValue(handshake.Substring(260), out list)) { byte[][] keypair = list[Global.Random.Next(0, list.Count - 1)]; ExchangeKey = keypair[0]; ((Base.Encryption.BNSAESEncryption)Crypt).Key = keypair[1]; SendExchangePacket(); } else { Logger.ShowWarning(string.Format("Не удается найти пару ключа:{0}", handshake)); Disconnect(); } } } public void SendExchangePacket() { SendPacketRaw(ExchangeKey, 0, ExchangeKey.Length); ready = true; }Для работы данного кода, понадобится зависимый класс для расшифровки AES ключа: public class BNSAESEncryption : KOI.Network.Encryption { Rijndael aes; public byte[] Key; ICryptoTransform enc; ICryptoTransform dec; public BNSAESEncryption() { aes = Rijndael.Create(); aes.Mode = CipherMode.ECB; enc = aes.CreateEncryptor(Key, new byte[16]); dec = aes.CreateDecryptor(Key, new byte[16]); } public override KOI.Network.Encryption Create() { return new BNSAESEncryption(); } public override void Encrypt(byte[] src, int offset, int len) { enc = aes.CreateEncryptor(Key, new byte[16]); enc.TransformBlock(src, offset, len, src, offset); } public override void Decrypt(byte[] src, int offset, int len) { byte[] buf = new byte[len + 16]; dec = aes.CreateDecryptor(Key, new byte[16]); src.CopyTo(buf, 0); dec.TransformBlock(buf, offset, len + 16, buf, offset); Array.Copy(buf, offset, src, offset, len); } } }Вот в кратце и все что вам нужно знать, для расшифровеи траффика. Ключи приведенные здесь 100% проверенные и будут работать до тех пор, пока система лобби не будет изменена в корне. А поскольку она не меняется с ЗБТ3, то и ключи также не меняются. У вас может возникнуть один вопрос. Откуда взять AES ключи, поскольку они открывают сессию и являются основным элементом крипта. Ответ: из olly. ![]() Пакеты лобби сервера:Свернуть ↑
Свернуть ↑Развернуть ↓
Поскольку мы используем свою сетевую модель, длина данных будет обозначаться следующим образом: Параметры GET/SET думаю всем понятно да? Тип данных: Byte - понятно 1 байт Short/Ushort - 2 байта Int/Uint - 4 байта Long/Ulong - 8 байтов. Выражение GetUshort(18) в нашей моделе означает, мы считываем 2 байта с 18 позиции следования. Если пакет имеет вид 01 00 00 00 01 то выражение GetUshort(2) означает что читаем следующие два байта 01 00 00 00 01. Надеюсь понятно. Погнали: SM_CHAR_CREATE / СM_CHAR_CREATE:Свернуть ↑
Свернуть ↑Развернуть ↓
public class SM_CHAR_CREATE : Packet<LobbyPacketOpcode> { ushort marker = 0; ushort marker2 = 0; public SM_CHAR_CREATE() { this.ID = LobbyPacketOpcode.SM_CHAR_CREATE; } public ActorPC Character { set { PutBytes(Utils.slot2GuidBytes(value.SlotID)); PutUShort(value.WorldID); PutShort((short)value.Name.Length); PutBytes(Encoding.Unicode.GetBytes(value.Name)); marker = offset; PutShort(0);//length PutShort(0);//length PutUShort((ushort)value.Appearence1.Length); PutBytes(value.Appearence1); //Race ID PutUShort(0); PutByte((byte)value.Race); //Gender ID PutUShort(1); PutByte((byte)value.Gender); //Class ID PutUShort(2); PutByte((byte)value.Job); //Appearence ID PutUShort(3); PutUShort((ushort)value.Appearence2.Length); PutBytes(value.Appearence2); //Name ID PutUShort(4); PutBytes(Encoding.Unicode.GetBytes(value.Name)); PutShort(0);//Terminator for String //Unknown ID PutUShort(5); PutUShort(0); //Map ID PutUShort(6); PutUInt(value.MapID); //X ID PutUShort(7); PutShort((short)value.X); //Y ID PutUShort(8); PutShort((short)value.Y); //Z ID PutUShort(9); PutShort((short)value.Z); //Level ID PutUShort(10); PutByte(value.Level); //Unknown ID PutUShort(11); PutUInt(0); //Health Points ID PutUShort(12); PutInt(value.MaxHP); //Unknown ID PutUShort(13); PutUShort(0); //Gold ID PutUShort(14); PutInt(value.Gold); //Weapon ID PutUShort(15); //if (value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Weapon] != null) // PutUInt(value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Weapon].ItemID); //else PutUInt(0); //Unknown ID PutUShort(16); PutUInt(0); //Costume ID PutUShort(17); //if (value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Costume] != null) // PutUInt(value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Costume].ItemID); //else PutUInt(0); //Unknown ID PutUShort(18); PutUInt(0); //Eye Accessory ID PutUShort(19); //if (value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Eye] != null) // PutUInt(value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Eye].ItemID); //else PutUInt(0); //Hat ID PutUShort(20); //if (value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Hat] != null) // PutUInt(value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.Hat].ItemID); //else PutUInt(0); //Costume Accessory ID PutUShort(21); //if (value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.CostumeAccessory] != null) // PutUInt(value.Inventory.Equipments[Base.Inventory.InventoryEquipSlot.CostumeAccessory].ItemID); //else PutUInt(0); marker2 = offset; PutUShort((ushort)(marker2 - (marker + 2)), marker); PutUShort((ushort)(marker2 - (marker + 2))); offset = marker2; PutInt(0); PutInt(0); PutInt(0); PutInt(0); } } } public class CM_CHAR_CREATE : Packet<LobbyPacketOpcode> { public CM_CHAR_CREATE() { this.ID = LobbyPacketOpcode.CM_CHAR_CREATE; } public ActorPC Character { get { ActorPC pc = new ActorPC(); byte[] guid = GetBytes(16, 2); for (int i = 0; i < 10; i++) { if (Conversions.bytes2HexString(guid).Equals(Conversions.bytes2HexString(Utils.slot2GuidBytes(i)))) { pc.SlotID = (byte)i; break; } } pc.WorldID = (byte)GetUShort(18); pc.Name = Encoding.Unicode.GetString(GetBytes((ushort)(GetUShort(20) * 2))); offset += 4; pc.Appearence1 = Conversions.HexStr2Bytes(Conversions.bytes2HexString(GetBytes(GetUShort())) + "7364777777636464776464646464646464"); offset += 2; pc.Race = (Race)GetByte(); offset += 2; pc.Gender = (Gender)GetByte(); offset += 2; pc.Job = (Job)GetByte(); offset += 2; pc.Appearence2 = GetBytes(GetUShort()); //Outside Packet pc.Level = 1; pc.MapID = 1101; pc.X = -3177; pc.Y = 9243; pc.Z = 599; pc.Dir = 45; pc.UISettings = ""; pc.InventorySize = 32; pc.HP = 99; pc.MaxHP = 99; switch (pc.Job) { case Job.Assassin: pc.MP = 0; pc.MaxMP = 10; break; case Job.ForceMaster: pc.MP = 10; pc.MaxMP = 10; break; case Job.KungfuMaster: pc.MP = 0; pc.MaxMP = 10; break; case Job.BladeMaster: default: pc.MP = 0; pc.MaxMP = 10; break; } return pc; } } public override Packet<LobbyPacketOpcode> New() { return new CM_CHAR_CREATE(); } public override void OnProcess(Session<LobbyPacketOpcode> client) { ((LobbySession)client).OnCharCreate(this); } } SM_CHAR_DELETE / CM_CHAR_DELETE:Свернуть ↑
Свернуть ↑Развернуть ↓
public class CM_CHAR_DELETE : Packet<LobbyPacketOpcode> { public CM_CHAR_DELETE() { this.ID = LobbyPacketOpcode.CM_CHAR_DELETE; } public byte[] Guid { get { return GetBytes(16, 2); } } public override Packet<LobbyPacketOpcode> New() { return new CM_CHAR_DELETE(); } public override void OnProcess(Session<LobbyPacketOpcode> client) { ((LobbySession)client).OnDeleteChar(this); } } public class SM_CHAR_DELETE : Packet<LobbyPacketOpcode> { public enum Reasons { Okay = 0, } public SM_CHAR_DELETE() { this.ID = LobbyPacketOpcode.SM_CHAR_DELETE; } public byte[] SlotGuid { set { PutBytes(value, 2); } } public Reasons Reason { set { PutByte(0,18); PutInt((int)value); } } } SM_CHARACTER_LIST / СM_CHARACTER_LIST:Свернуть ↑
Свернуть ↑Развернуть ↓
public class SM_CHARACTER_LIST : Packet<LobbyPacketOpcode> { ushort marker = 0; ushort marker2 = 0; public SM_CHARACTER_LIST() { this.ID = LobbyPacketOpcode.SM_CHARACTER_LIST; } public List<ActorPC> Characters { set { PutUShort((ushort)value.Count, 6); foreach (ActorPC i in value) { PutBytes(Utils.slot2GuidBytes(i.SlotID)); PutUShort(i.WorldID); PutShort((short)i.Name.Length); PutBytes(Encoding.Unicode.GetBytes(i.Name)); marker = offset; PutShort(0);//length PutShort(0);//length PutUShort((ushort)i.Appearence1.Length); PutBytes(i.Appearence1); //Race ID PutUShort(0); PutByte((byte)i.Race); //Gender ID PutUShort(1); PutByte((byte)i.Gender); //Class ID PutUShort(2); PutByte((byte)i.Job); //Appearence ID PutUShort(3); PutUShort((ushort)i.Appearence2.Length); PutBytes(i.Appearence2); //Name ID PutUShort(4); PutBytes(Encoding.Unicode.GetBytes(i.Name)); PutShort(0);//Terminator for String //Unknown ID PutUShort(5); PutUShort(0); //Map ID PutUShort(6); PutUInt(i.MapID); //X ID PutUShort(7); PutShort((short)i.X); //Y ID PutUShort(8); PutShort((short)i.Y); //Z ID PutUShort(9); PutShort((short)i.Z); //Level ID PutUShort(10); PutByte(i.Level); //Unknown ID PutUShort(11); PutUInt(0); //Health Points ID PutUShort(12); PutInt(i.MaxHP); //Unknown ID PutUShort(13); PutUShort(0); //Gold ID PutUShort(14); PutInt(i.Gold); //Weapon ID PutUShort(15); if (i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.hand - 1] != null) PutUInt(i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.hand - 1].ItemID); else PutUInt(0); //Equip Hand //Alternate Hand PutUShort(16); PutUInt(0); //Costume ID PutUShort(17); if (i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.body - 1] != null) PutUInt(i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.body - 1].ItemID); else PutUInt(0);//Equip Body //Ear PutUShort(18); PutUInt(0); //Eye Accessory ID PutUShort(19); if (i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.eye - 1] != null) PutUInt(i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.eye - 1].ItemID); else PutUInt(0);//Equip Eye //Hat ID PutUShort(20); if (i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.head - 1] != null) PutUInt(i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.head - 1].ItemID); else PutUInt(0);//Equip Head //Costume Accessory ID PutUShort(21); if (i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.body_attach - 1] != null) PutUInt(i.Inventory.Container[Containers.Equipment][(int)InventoryEquipSlot.body_attach - 1].ItemID); else PutUInt(0);//Equip Body Attach marker2 = offset; PutUShort((ushort)(marker2 - (marker + 2)), marker); PutUShort((ushort)(marker2 - (marker + 2))); offset = marker2; PutInt(0); PutInt(0); PutInt(0); PutByte(0); } PutUInt((uint)offset - 6, 2); } } } public class CM_CHARACTER_LIST : Packet<LobbyPacketOpcode> { public CM_CHARACTER_LIST() { this.ID = LobbyPacketOpcode.CM_CHARACTER_LIST; } public override Packet<LobbyPacketOpcode> New() { return new CM_CHARACTER_LIST(); } public override void OnProcess(Session<LobbyPacketOpcode> client) { ((LobbySession)client).OnCharacterList(this); } } SM_REQUEST_LOGIN / СM_REQUEST_LOGIN:Свернуть ↑
Свернуть ↑Развернуть ↓
public class SM_REQUEST_LOGIN : Packet<LobbyPacketOpcode> { public SM_REQUEST_LOGIN() { this.ID = LobbyPacketOpcode.SM_REQUEST_LOGIN; } public ulong CharID { set { PutULong(value, 2); } } } public class CM_REQUEST_LOGIN : Packet<LobbyPacketOpcode> { public CM_REQUEST_LOGIN() { this.ID = LobbyPacketOpcode.CM_REQUEST_LOGIN; } public byte[] Guid { get { return GetBytes(16, 2); } } public override Packet<LobbyPacketOpcode> New() { return new CM_REQUEST_LOGIN(); } public override void OnProcess(Session<LobbyPacketOpcode> client) { ((LobbySession)client).OnRequestLogin(this); } } CM_AUTH / SM_AUTH_RESULT:Свернуть ↑
Свернуть ↑Развернуть ↓
public class CM_AUTH : Packet<LobbyPacketOpcode> { public CM_AUTH() { this.ID = LobbyPacketOpcode.CM_AUTH; } public Guid Token { get { return new Guid(GetBytes(16, 2)); } } public Guid AccountID { get { return new Guid(GetBytes(16, 18)); } } public override Packet<LobbyPacketOpcode> New() { return new CM_AUTH(); } public override void OnProcess(Session<LobbyPacketOpcode> client) { ((LobbySession)client).OnLoginAuth(this); } } public class SM_AUTH_RESULT : Packet<LobbyPacketOpcode> { public SM_AUTH_RESULT() { this.ID = LobbyPacketOpcode.SM_AUTH_RESULT; Unknown1 = 2677811; AES_KEY = Conversions.HexStr2Bytes("00000000000000000000000000000000"); } public int Unknown1 { set { PutInt(value, 2); } } public byte[] AES_KEY { set { PutShort(16, 35); PutBytes(value); } } } CM_SERVER_LIST / SM_SERVER_LIST:Свернуть ↑
Свернуть ↑Развернуть ↓
public class CM_SERVER_LIST : Packet<LobbyPacketOpcode> { public CM_SERVER_LIST() { this.ID = LobbyPacketOpcode.CM_SERVER_LIST; } public override Packet<LobbyPacketOpcode> New() { return new CM_SERVER_LIST(); } public override void OnProcess(Session<LobbyPacketOpcode> client) { ((LobbySession)client).OnWorldListRequest(this); } } public class SM_SERVER_LIST : Packet<LobbyPacketOpcode> { public SM_SERVER_LIST() { this.ID = LobbyPacketOpcode.SM_SERVER_LIST; } public ICollection<World> Worlds { set { PutUShort((ushort)value.Count, 6); foreach (World w in value) { PutUShort((ushort)w.ID); PutUShort((ushort)w.ID); PutUShort((ushort)w.Name.Length); PutBytes(Encoding.Unicode.GetBytes(w.Name)); PutByte(1);//Unknown PutByte(1);//Unknown PutBytes(w.IPAsArray); PutUShort(w.Port); PutInt(0);//Unknown PutByte(1);// 0 offline 1 online PutByte(1);//Unknown PutInt(1);//Unknown PutByte(1);//Unknown PutUShort((ushort)w.MaxPlayerCount); PutUShort((ushort)w.PlayerCount); PutInt(w.MaxPlayerCount); } PutInt((int)this.Length - 6, 2); } } } Неопределенные пакеты UNK1,2,3:Свернуть ↑
Свернуть ↑Развернуть ↓
public class SM_Unk1 : Packet<LobbyPacketOpcode> { public SM_Unk1() { this.ID = LobbyPacketOpcode.SM_Unk1; Unknown1 = 0x0; } public int Unknown1 { set { PutInt(value, 2); } } } public class SM_Unk2 : Packet<LobbyPacketOpcode> { public SM_Unk2() { this.ID = LobbyPacketOpcode.SM_Unk2; Unknown1 = 0x7; Unknown2 = 0x0; Unknown3 = 25601; } public short Unknown1 { set { PutInt(value, 2); } } public int Unknown2 { set { PutInt(value, 9); } } public int Unknown3 { set { PutInt(value, 6); } } } public class SM_Unk3 : Packet<LobbyPacketOpcode> { public SM_Unk3() { this.ID = LobbyPacketOpcode.SM_Unk3; Unknown1 = 0x2; Unknown2 = 0x0; } public short Unknown1 { set { PutInt(value, 2); } } public int Unknown2 { set { PutInt(value, 4); } } } Опкоды:Свернуть ↑
Свернуть ↑Развернуть ↓
public enum LobbyPacketOpcode { CM_AUTH = 0x3, SM_AUTH_RESULT, CM_KEEP_ALIVE = 0x9, CM_REQUEST_LOGIN = 0xD, SM_REQUEST_LOGIN, CM_SERVER_LIST = 0x18, SM_SERVER_LIST, CM_CHARACTER_LIST = 0x1B, SM_CHARACTER_LIST, CM_CHAR_CREATE = 0x24, SM_CHAR_CREATE, SM_CHAR_CREATE_FAILED, CM_CHAR_DELETE, SM_CHAR_DELETE, SM_CHAR_DELETE_FINISH = 0x2A, CM_CHAR_DELETE_CANCEL, SM_CHAR_DELETE_CANCEL, CM_Unk1 = 0x35, SM_Unk1, CM_Unk2 = 0x3E, SM_Unk2, CM_Unk3 = 0x44, SM_Unk3, } Пример обработчика:Свернуть ↑
Свернуть ↑Развернуть ↓
public partial class LobbySession : Session<LobbyPacketOpcode> { Guid accountID; Account acc; public void OnLoginAuth(CM_AUTH p) { accountID = p.AccountID; Logger.ShowInfo(string.Format("Account:{0} is loging in", p.AccountID)); AccountSession.Instance.RequestAccountInfo(accountID.ToUInt(), this); } public void OnGotAccountInfo(Base.Packets.AccountServer.AccountLoginResult result, Account acc) { Logger.ShowInfo(string.Format("Load Account info for {0}({1}):{2}", accountID, accountID.ToUInt(), result)); if (result == Base.Packets.AccountServer.AccountLoginResult.OK) { this.acc = acc; SM_AUTH_RESULT p1 = new SM_AUTH_RESULT(); this.Network.SendPacket(p1); } } public void OnWorldListRequest(CM_SERVER_LIST p) { SM_SERVER_LIST p1 = new SM_SERVER_LIST(); p1.Worlds = WorldManager.Instance.Worlds.Values; this.Network.SendPacket(p1); } }Думаю вы в этом не нуждаетесь, так как напишите нечто свое... На этом лобби сервер заканчивается. Человеческая просьба. Если вы не ставите плюсики или "спасибо", то хотя бы не пишите гадости про код. Не нравится? Не комментируйте, пройдите мимо, закройте эту закладку, а лучше забудьте. п.с. чуть позже выложу сниффы пакетов лобби сервера, уже раскодированные под последнюю версию клиента. Сможете сами их ковырять. Добавлено через 1 час 33 минуты Далее выкладываю свежий снифф лобби сервера: Пакеты Лобби Сервера (снифф):Свернуть ↑
Свернуть ↑Развернуть ↓
Sender:Client Time Captured: [2014.03.20 19:07:22] Opcode:0x0003 Name:CM_AUTH Length:34 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 03 00 FC 10 85 0F CA 77 08 42 99 44 31 6E B2 8F 33 9B E9 5C B0 A6 A9 34 11 E1 B0 16 E6 1F 13 5E 99 2F Sender:Server Time Captured: [2014.03.20 19:07:22] Opcode:0x0004 Name:SM_AUTH_RESULT Length:53 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 04 00 99 FC 12 00 00 00 00 00 00 68 26 F3 7F BC B6 EE 45 82 40 27 3D 94 0B 2B BA DE 7A EE 76 44 2F 00 00 10 00 FE 56 96 2F C5 90 7E F7 33 3A E7 C6 E2 13 E2 7E Sender:Client Time Captured: [2014.03.20 19:07:52] Opcode:0x0009 Name:CM_KEEP_ALIVE Length:2 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 09 00 Sender:Client Time Captured: [2014.03.20 19:07:22] Opcode:0x0018 Name:CM_SERVER_LIST Length:2 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 18 00 Sender:Client Time Captured: [2014.03.20 19:07:23] Opcode:0x001B Name:CM_CHARACTER_LIST Length:2 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 1B 00 Sender:Server Time Captured: [2014.03.20 19:07:24] Opcode:0x001C Name:SM_CHARACTER_LIST Data Length:554 Character Count:2 Character GUID:FCF17A05B6B611E1A49DE41F136C9588 World ID:5 Name:보확찢 Appearence1:6363634E63737364777777636464776464646464646464 Race:Jin Gender:Male Class:BladeMaster Appearence2:03000209010C18030E01020102020103070601012B0000000043003E34000E2605B0ECF10AC9E7F600F1000FDDBF0000F1140A00000F05D3141EF628C4BADD0000000005000000000000000000000000000000000000000000000000 Name Null Terminated:보확찢 Unknown 0x05:0 Map ID:1101 X:0 Y:0 Z:0 Level:1 Unknown 0x0B:0 HP:229 Unknown 0x0D:0 Gold:0 Weapon ID:0 Unknown 0x10:0 Costume ID:0 Unknown 0x12:0 Eye Accessory ID:0 Hat ID:0 Costume Accessory ID:0 Last Login: 1970.01.01 02:00:00 Unknown Bytes:0000000002D0000000 Character GUID:B21E098CB05111E3A20BE41F136C9588 World ID:15 Name:lunka Appearence1:6363634E63737364777777636464776464646464646464 Race:Lyn Gender:Female Class:ForceMaster Appearence2:01020A06010C05031002180302010202010102003900000000000E0000000E05050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 Name Null Terminated:lunka Unknown 0x05:0 Map ID:1101 X:0 Y:0 Z:0 Level:1 Unknown 0x0B:0 HP:206 Unknown 0x0D:0 Gold:0 Weapon ID:0 Unknown 0x10:0 Costume ID:0 Unknown 0x12:0 Eye Accessory ID:0 Hat ID:0 Costume Accessory ID:0 Last Login: 1970.01.01 02:00:00 Unknown Bytes:000000000100000000 Length:560 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 1C 00 2A 02 00 00 02 00 FC F1 7A 05 B6 B6 11 E1 A4 9D E4 1F 13 6C 95 88 05 00 03 00 F4 BC 55 D6 22 CC E7 00 E7 00 17 00 63 63 63 4E 63 73 73 64 77 77 77 63 64 64 77 64 64 64 64 64 64 64 64 00 00 04 01 00 01 02 00 01 03 00 5C 00 03 00 02 09 01 0C 18 03 0E 01 02 01 02 02 01 03 07 06 01 01 2B 00 00 00 00 43 00 3E 34 00 0E 26 05 B0 EC F1 0A C9 E7 F6 00 F1 00 0F DD BF 00 00 F1 14 0A 00 00 0F 05 D3 14 1E F6 28 C4 BA DD 00 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00 F4 BC 55 D6 22 CC 00 00 05 00 00 00 06 00 4D 04 00 00 07 00 00 00 08 00 00 00 09 00 00 00 0A 00 01 0B 00 00 00 00 00 0C 00 E5 00 00 00 0D 00 00 00 0E 00 00 00 00 00 0F 00 00 00 00 00 10 00 00 00 00 00 11 00 00 00 00 00 12 00 00 00 00 00 13 00 00 00 00 00 14 00 00 00 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 02 D0 00 00 00 B2 1E 09 8C B0 51 11 E3 A2 0B E4 1F 13 6C 95 88 0F 00 05 00 6C 00 75 00 6E 00 6B 00 61 00 EB 00 EB 00 17 00 63 63 63 4E 63 73 73 64 77 77 77 63 64 64 77 64 64 64 64 64 64 64 64 00 00 03 01 00 02 02 00 03 03 00 5C 00 01 02 0A 06 01 0C 05 03 10 02 18 03 02 01 02 02 01 01 02 00 39 00 00 00 00 00 0E 00 00 00 0E 05 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00 6C 00 75 00 6E 00 6B 00 61 00 00 00 05 00 00 00 06 00 4D 04 00 00 07 00 00 00 08 00 00 00 09 00 00 00 0A 00 01 0B 00 00 00 00 00 0C 00 CE 00 00 00 0D 00 00 00 0E 00 00 00 00 00 0F 00 00 00 00 00 10 00 00 00 00 00 11 00 00 00 00 00 12 00 00 00 00 00 13 00 00 00 00 00 14 00 00 00 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 Sender:Client Time Captured: [2014.03.20 19:07:46] Opcode:0x0024 Name:CM_CHAR_CREATE Length:175 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 24 00 B2 1E 09 8D B0 51 11 E3 A2 0B E4 1F 13 6C 95 88 1A 00 08 00 6C 00 75 00 6E 00 61 00 6C 00 75 00 6E 00 61 00 87 00 87 00 06 00 63 63 63 4E 63 73 00 00 03 01 00 02 02 00 03 03 00 5C 00 01 02 0A 06 01 0C 05 03 10 02 18 03 02 01 02 02 01 01 02 00 39 00 00 00 00 00 0E 00 00 00 0E 05 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00 6C 00 75 00 6E 00 61 00 6C 00 75 00 6E 00 61 00 00 00 Sender:Server Time Captured: [2014.03.20 19:07:52] Opcode:0x0025 Name:SM_CHAR_CREATE Length:298 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 25 00 B2 1E 09 8D B0 51 11 E3 A2 0B E4 1F 13 6C 95 88 1A 00 09 00 6C 00 75 00 6E 00 61 00 6C 00 75 00 6E 00 61 00 6C 00 F3 00 F3 00 17 00 63 63 63 4E 63 73 73 64 77 77 77 63 64 64 77 64 64 64 64 64 64 64 64 00 00 03 01 00 02 02 00 03 03 00 5C 00 01 02 0A 06 01 0C 05 03 10 02 18 03 02 01 02 02 01 01 02 00 39 00 00 00 00 00 0E 00 00 00 0E 05 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 00 6C 00 75 00 6E 00 61 00 6C 00 75 00 6E 00 61 00 6C 00 00 00 05 00 00 00 06 00 4D 04 00 00 07 00 00 00 08 00 00 00 09 00 00 00 0A 00 01 0B 00 00 00 00 00 0C 00 CE 00 00 00 0D 00 00 00 0E 00 00 00 00 00 0F 00 00 00 00 00 10 00 00 00 00 00 11 00 00 00 00 00 12 00 00 00 00 00 13 00 00 00 00 00 14 00 00 00 00 00 15 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F8 01 00 00 Sender:Server Time Captured: [2014.03.20 19:07:46] Opcode:0x0026 Name:SM_CHAR_CREATE_FAILED Length:20 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 26 00 B2 1E 09 8D B0 51 11 E3 A2 0B E4 1F 13 6C 95 88 7A 00 Sender:Client Time Captured: [2014.03.20 19:08:07] Opcode:0x0027 Name:CM_CHAR_DELETE 0xE31151B08C091EB2 Length:18 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 27 00 B2 1E 09 8C B0 51 11 E3 A2 0B E4 1F 13 6C 95 88 Sender:Server Time Captured: [2014.03.20 19:08:07] Opcode:0x0028 Name:SM_CHAR_DELETE Length:23 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 28 00 B2 1E 09 8C B0 51 11 E3 A2 0B E4 1F 13 6C 95 88 01 68 01 00 00 Sender:Client Time Captured: [2014.03.20 19:08:13] Opcode:0x002B Name:CM_CHAR_DELETE_CANCEL Length:18 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 2B 00 FC F1 7A 05 B6 B6 11 E1 A4 9D E4 1F 13 6C 95 88 Sender:Server Time Captured: [2014.03.20 19:08:14] Opcode:0x002C Name:SM_CHAR_DELETE_CANCEL Length:18 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 2C 00 FC F1 7A 05 B6 B6 11 E1 A4 9D E4 1F 13 6C 95 88 Sender:Client Time Captured: [2014.03.20 19:07:23] Opcode:0x0035 Name:CM_Unk1 Length:2 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 35 00 Sender:Server Time Captured: [2014.03.20 19:07:23] Opcode:0x0036 Name:SM_Unk1 Length:6 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 36 00 00 00 00 00 Sender:Client Time Captured: [2014.03.20 19:07:23] Opcode:0x003E Name:CM_Unk2 Length:11 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 3E 00 01 00 00 00 00 00 00 00 00 Sender:Server Time Captured: [2014.03.20 19:07:23] Opcode:0x003F Name:SM_Unk2 Length:13 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 3F 00 07 00 00 00 01 64 00 00 00 00 00 Sender:Client Time Captured: [2014.03.20 19:07:24] Opcode:0x0044 Name:CM_Unk3 Length:2 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 44 00 Sender:Server Time Captured: [2014.03.20 19:07:24] Opcode:0x0045 Name:SM_Unk3 Length:8 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 45 00 02 00 00 00 00 00 Sender:Client Time Captured: [2014.03.20 19:07:23] Opcode:0x0059 Name:89 Length:2 Data: ================================================= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ================================================= 59 00 Первый байт означает опкод, второй байт является определяющим тип пакета. Таким образом грубо говоря, первые два байта можно использовать как опкод. Пакеты читаются очень легко, поэтому читающий да прочтет! Последний раз редактировалось luna9966; 20.03.2014 в 20:27. Причина: Добавлено сообщение |
![]() |
![]() |
Сказали спасибо: |
![]() |
#5 |
Пользователь
|
![]()
А что за аргумент checkHash в методе public void OnAuthKeyData(byte[] exchangeKey, string checkHash, int serial)? Как он генерируется?
В роуте /Auth/KeyData я получают 2массива каких то. По первому - я генерирую ключики: Код:
handler.getKeyExchange().generateKey( AbstractKeyExchange.Mode.SERVER, exchangeKey ); String hash = handler.getKeyExchange().getAuthentication(); Но вот что за checkHash я так и не понял От клиент приходит вторым массивом какой то ключ 32 байта То это далеко не текст.. Помогите плиз) п.с. Код:
package openbns.loginserver.net.client.impl; import io.netty.buffer.ByteBufInputStream; import openbns.commons.crypt.AbstractKeyExchange; import openbns.commons.crypt.CryptUtil; import openbns.commons.xml.StsXStream; import openbns.loginserver.net.client.AbstractRequestPacket; import openbns.loginserver.net.client.dto.KeyDataDTO; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * Created with IntelliJ IDEA. * User: Eugene Chipachenko * Date: 27.01.14 * Time: 21:14 */ public class RequestKeyData extends AbstractRequestPacket { private static final Log log = LogFactory.getLog( RequestKeyData.class ); private KeyDataDTO keyData; @Override public void read() { StsXStream stream = new StsXStream(); stream.processAnnotations( KeyDataDTO.class ); keyData = (KeyDataDTO) stream.fromXML( new ByteBufInputStream( buf ) ); log.debug( "Read from client object: " + keyData ); } @Override public void execute() { byte[] data = CryptUtil.base64( keyData.getKeyData() ); ByteBuffer bf = ByteBuffer.wrap( data ); bf.order( ByteOrder.LITTLE_ENDIAN ); int size1 = bf.getInt(); byte[] exchangeKey = new byte[ size1 ]; bf.get( exchangeKey ); int size2 = bf.getInt(); byte[] checkHash = new byte[ size2 ]; bf.get( checkHash ); try { handler.getKeyExchange().generateKey( AbstractKeyExchange.Mode.SERVER, exchangeKey ); String hash = handler.getKeyExchange().getAuthentication(); // Что я тут забыл то?? System.out.println( hash ); } catch( Exception e ) { e.printStackTrace(); } System.out.println( new String( exchangeKey ) ); System.out.println( new String( checkHash ) ); } } |
![]() |
![]() |
![]() |
#6 | |
Пользователь
|
![]() Цитата:
CM_AUTH_KEYDATA:Свернуть ↑
Свернуть ↑Развернуть ↓
public CM_AUTH_KEY_DATA() { this.ID = LoginPacketOpcode.CM_AUTH_KEY_DATA; } public override Packet<LoginPacketOpcode> New() { return new CM_AUTH_KEY_DATA(); } public override void OnProcess(Session<LoginPacketOpcode> client) { XmlDocument xml = ReadLoginPacket(); XmlElement root; XmlNodeList list; byte[] keyData = null; root = xml["Request"]; list = root.ChildNodes; foreach (object j in list) { XmlElement i; if (j.GetType() != typeof(XmlElement)) continue; i = (XmlElement)j; switch (i.Name.ToLower()) { case "keydata": keyData = Convert.FromBase64String(i.InnerText); break; } } xml = null; System.IO.MemoryStream ms = new System.IO.MemoryStream(keyData); System.IO.BinaryReader br = new System.IO.BinaryReader(ms); byte[] exchangeKey = br.ReadBytes(br.ReadInt32()); byte[] hash = br.ReadBytes(br.ReadInt32()); string checkHash = Convert.ToBase64String(hash); ((LoginSession)client).OnAuthKeyData(exchangeKey, checkHash, Serial); } } Ключ который приходит в 32 байтах считываем путем: byte[] hash = br.ReadBytes(br.ReadInt32()); И затем переводим в base64 string checkHash = Convert.ToBase64String(hash); получая значение checkHash Желательно в этой теме не обсуждать строительство эмулятора, лучше писать уже в существующих темах, чтобы данная тема была максимально чистой и понятной. |
|
![]() |
![]() |
![]() |
#7 |
Пользователь
|
![]()
you started this for which client ?
|
![]() |
![]() |
![]() |
#8 |
Пользователь
|
![]()
I already told you, take one thread and put all your questions there. Don't make a MESS on forum. OK? Moreover current thread is suppose to be guide-like, I strongly don't recommend you to put any dicussion here. Otherwise I will ask moderator to clean this thread up.
The answer you are looking for, there is no certain client. All information above is the same for all BNS versions. So doesn't matter what version you will choose, the login server, lobby always the same. The game part would be slightly different because of opcodes which are different for each update. The info above gives you basics to understand how things should work, but it never meant to be a source. And you should understand too, why do you need a client without GG and Themida. First of all, full BNS encryption consists of RSA and Deffie-Hellman with Ripemd128 cipher which later is used to generate 16 bytes AES key, which will be used for main packets encryption. Got it? To get AES keys you don't need to reverse RSA stuff, you just need to write small hooker for AES Encryption function and you will get all of AES keys which are used in encryption process. Once you did it, you can decrypt all packets and see the structure... If you are good with C++ you can use MS Detour, if C# then use EasyHook. That's it. |
![]() |
![]() |
![]() |
#9 |
Пользователь
|
![]()
An English translation would be great!
(i know a little bit russian but its not enough tho :P) |
![]() |
![]() |
![]() |
Метки |
blade and soul, bns emulator, bns server, crypt, network ![]() |
Здесь присутствуют: 1 (пользователей: 0 , гостей: 1) | |
Опции темы | |
|
|