服务端vb.net编写参考资料
2013-03-27 21:06:37 作者:玩家 来源: 浏览次数:0
      
      
        
            
                网可以说是现在市面上最红火的一个网络游戏了,不过从游戏性还是耐玩性,都强过不少其他网游。现在的网游,很多一部分都存在着私服,但是这些大多是泄漏出来的官方服务器端,所以只需稍加修改就可以架设一个完美的私服。不过魔兽世界一直以来只有模拟器,这些模拟器都是不少编程和破解高手费尽心血破解魔兽客户端,研究封包慢慢写出来的服务器端。但是就算市面上最好的模拟器(要收费的)WoWEmu,模拟度也只能达到大概60%左右,存在不少bug。还有更重要的是,由于WoWEmu使用的是文本文件数据库,还有也许服务器端框架上的问题,即便是非常强劲的服务器,都只能带起很少的在线人数,还有就是这个模拟器还是收费的。现在还有另外几款WoW的模拟器,例如RunWoW,WoWWoW,等,有些甚至还是SQL版,不过这些服务器端的模拟度都不如WoWEmu,所以使用的人也很少。
国内现在应该有很多人有兴趣编写一个自己的魔兽服务器端,但是碍于缺乏资料,不知道该从何下手,网上的资料却又非常不全和不详细,能找得到的唯一能编译的代码是非常古老的WoWDaemon,但是这东西是针对WoW Beta的,所以对现在的WoW来说,没多少价值。网上还有流传着一些反编译WoWWoW出来的代码,但是这个代码无法编译,而且因为用的代码混淆器,要想从中得到一些自己想要的东西是非常费力的。所以本人现在想把我近段时间对魔兽世界的研究结果发布出来,希望能对大家有所帮助。
本篇文章中的所有例子和讲解都是以vb.net为基础的,对c#的读者问题应该也不大。使用c++的估计也只是关心相关算法,所以也没有太大问题。适用客户端为1.80-1.92
首先,我会跟大家讲解一下魔兽世界在登陆过程中的流程。首先,客户端会发送一个包含用户名和客户端版本,语言等信息的包给服务器端,然后服务器端验证版本是否符合,用户名是否存在,然后将用户名和密码通过一个算法转换成一串16进制数据,发给客户端当验证码。客户端这时会使用这个验证码,跟客户输入的用户名和密码通过运算得到另外一个验证数据,再连同随机产生的通讯密码一同发给服务器端,服务器端再通过通讯密码,和第一次发送的验证码再通过一番计算,如果跟这次客户端发过来的验证数据一模一样,则验证通过,然后把验证码记录下来,供游戏服务器验证客户端是否经过帐号验证使用。
下面我会跟大家讲各个过程的封包结构和算法
1、  第一个封包(客户端发送用户名和版本信息)
这个封包的结构如下:
Public Structure ClientLogonChallenge
        Dim cmd As MyWoW.LoginServer.Packet.LogonOpcode  '            // OP code = CMD_AUTH_LOGON_CHALLENGE
        Dim [error] As MyWoW.LoginServer.Packet.LogonErrorCode    '            // 0x02
        Dim size As UInt16 '              // size of the rest of packet, without this part
        Dim gamename() As Char    '  // "WoW"
        Dim version() As Byte '      // 0x01 08 00 -> (1.8.0)
        Dim build As UInt16 '             // 0x7F12 -> 4735
        Dim platform() As Char   '   // 68x\0 -> "x86"
        Dim os() As Char   '              // niW\0 -> "Win"
        Dim localization() As Char '// BGne -> 'enGB'
        Dim timezone_bias As UInt32 ' // 0x78 00 00 00
        Dim ip() As Byte '           // client ip: 0x7F 00 00 01
        Dim acclen As Byte '              // length of account name (without zero-char)
        Dim acc() As Char '          // upcased account name (without zero-char)
End Structure
其中cmd是登陆过程使用的Opcode(即命令代码),登陆过程的Opcode列表如下:
Public Enum LogonOpcode As Byte
        CMD_AUTH_LOGON_CHALLENGE = &H0  ' // client
        CMD_AUTH_LOGON_PROOF = &H1   ' // client
        CMD_AUTH_RECONNECT_CHALLENGE = &H2 ' // client
        CMD_AUTH_RECONNECT_PROOF = &H3  ' // client
        CMD_REALM_LIST = &H10    ' // client
        CMD_XFER_INITIATE = &H30   ' // client? from server
        CMD_XFER_DATA = &H31    ' // client? from server
        CMD_XFER_ACCEPT = &H32    ' // not official name, from client
        CMD_XFER_RESUME = &H33    ' // not official name, from client
        CMD_XFER_CANCEL = &H34    ' // not official name, from client
        '// unknown:
        CMD_GRUNT_AUTH_CHALLENGE = &H0   ' // server
        CMD_GRUNT_AUTH_VERIFY = &H2   '   // server
        CMD_GRUNT_CONN_PING = &H10   ' // server
        CMD_GRUNT_CONN_PONG = &H11   ' // server
        CMD_GRUNT_HELLO = &H20    ' // server
        CMD_GRUNT_PROVESESSION = &H21  ' // server
        CMD_GRUNT_KICK = &H24    ' // server
End Enum
error为错误代码,列表如下:
Public Enum LogonErrorCode As Byte
        '// LOGIN_STATE_FAILED:
        LOGIN_FAILED = 1 ' // 2, B, C, D // "Unable to connect"
        LOGIN_BANNED = 3 ' // "This World of Warcraft account has been closed and is no longer in service -- Please check the registered email address of this account for further information."; -- This is the error message players get when trying to log in with a banned account.
        LOGIN_UNKNOWN_ACCOUNT = 4 ' // 5 // "The information you have entered is not valid.  Please check the spelling of the account name and password.  If you need help in retrieving a lost or stolen password and account, see www.worldofwarcraft.com for more information.";
        LOGIN_ALREADYONLINE = 6 ' // "This account is already logged into World of Warcraft.  Please check the spelling and try again.";
        LOGIN_NOTIME = 7 ' // "You have used up your prepaid time for this account. Please purchase more to continue playing";
        LOGIN_DBBUSY = 8 ' // "Could not log in to World of Warcraft at this time.  Please try again later.";
        LOGIN_BADVERSION = 9 ' // "Unable to validate game version.  This may be caused by file corruption or the interference of another program.  Please visitwww.blizzard.com/support/wow/ for more information and possible solutions to this issue.";
        LOGIN_PARENTALCONTROL = &HF ' // "17"="LOGIN_PARENTALCONTROL" // "Access to this account has been blocked by parental controls.  Your settings may be changed in your account preferences at http://www.worldofwarcraft.com.";
        '// LOGIN_STATE_AUTHENTICATED:
        LOGIN_OK = 0 ' // E
        '// LOGIN_STATE_DOWNLOADFILE, LOGIN_OK
        LOGIN_DOWNLOADFILE = &HA ' // not official name
End Enum
size为无符号16位整数型,表示除了封包头部(命令码,错误码,和封包大小)之外的剩余封包大小
gamename一直都为WoW,不过4个字节长度,第一个字节为0
version表示客户端版本号,3字节,一个字节表示一个小节,比如1.8.0则为01 08 00
build是客户端的build号,16位无符号整数,如:7F12就是4735。
platform是系统信息,一般为“68x”即x86,4字节
os是操作系统,如niW则为Win,4字节
localization是客户端语言和区域信息,英语英国则为BGne,即enGB,4字节
timezone就是时区,没什么好解释,4字节
ip是客户端ip地址,4字节,如7F 00 00 1为127.0.0.001
acclen为帐号名长度,8位无符号整数,1字节
acc为用户输入的用户名,全部大写
其实这么多信息,真正重要的是版本号和用户名,其他处不处理看个人喜好。
服务器的响应封包结构如下:
Public Structure SServerLogonChallenge_Ok
        Dim cmd As Packet.LogonOpcode ';           // 0x00 -> OPcode = CMD_AUTH_LOGON_CHALLENGE ? CMD_GRUNT_AUTH_CHALLENGE
        Dim [error] As Packet.LogonErrorCode ' ;             // 0 -> ok = LOGIN_OK
        Dim unk1 As Byte '           // 0x00
        Dim B() As Byte  '           // calculated by server
        Dim g_len As Byte '          // 0x01
        Dim g As Byte '          // 0x07 (const)
        Dim N_len As Byte '          // 0x20
        Dim N As BigInteger  '            // const
        Dim salt() As Byte '         // random
        Dim unk2() As Byte '         //1: BA 79 D4 8D - BF FC BF AD - 8C B4 EC B3 - 75 C5 96 05
End Structure
其中cmd还是CMD_AUTH_LOGON_CHALLENGE,没变。error不管是否版本错误或者帐号不存在,都通通回复LOGIN_OK,unk1不知道有什么用,0即可。
下面讲解B的算法:
要算B,首先得生成几个其他得值,首先是N,这个是一个不变的常数,也许大家已经注意到了,N的类型是BigInteger,B和SALT都是转换成16进制的BigInteger,vb.net或者c#本身不支持这个数据类型,要实现这个就要用到一个附加的类。这个类我稍微修改了一下,这样可以适用于WoW的运算,点击这里可以下载到。N用16进制表示出来为:"894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7",长度是32字节。然后是salt,是一个随机生成的一个32字节长的整数。g为常数7。
现在开始计算部分:
首先,我们需要一个临时字符串,用来保存从帐号数据库里取得的正确的用户名和密码(例如,用户名abc,密码def,那么这个字符串为:”ABC:DEF”)。此外我们还需要另外一个Byte()类变量,比如Dim TempHash() As Byte,TempHash等于刚才那个临时字符串的sha1值。现在将tempHash反转(reserve)然后用他创建一个新的临时BigInteger变量x。
现在我们需要另外一些临时变量:tmp As BigInteger, v As BigInteger, k As New BigInteger,其中tmp等于N反转后生成的biginteger,k等于3,v=(x^g) mod tmp
(运算符都为vb运算符,^是指数,mod是求余数,c#或者c++请自行修改)
现在需要随机生成另外一个20字节长度的大整数:Dim b As BigInteger。注意,这里的b是个临时变量,并不是最后计算结果B。
现在从新给tmp赋值:tmp=(k * v + (b ^ g) mod tmp) mod tmp
计算结果B等于tmp的16进制表达方式然后反转。
封包最后一个变量,unk2,随机生成一个16字节长度大整数转换成16进制即可
下面是我写的处理第一个封包的代码,没优化,所以有些地方有点不合理,不过不影响使用:
Private Shared Function ProcessClientLogonChallenge(ByVal sock As MyWoW.LoginServer.Client, ByVal data As ClientLogonChallenge) As Boolean
            Dim senddata As Packet.PacketSendManager.Packet
            Dim sendpacket As SServerLogonChallenge_Ok
            Dim tick As Integer = GetTickCount
            Dim x As BigInteger, tmp As New BigInteger, temphashb() As Byte
            sock.Challenged = True
            sendpacket.error = LogonErrorCode.LOGIN_OK
            sock.PendingError = LogonErrorCode.LOGIN_OK
            If data.acclen >= 30 Then
                sock.PendingError = LogonErrorCode.LOGIN_UNKNOWN_ACCOUNT
                'Return True
            End If
            If Convert.ToInt16(data.build) < m_minbuild Or Convert.ToInt16(data.build) > m_maxbuild Then
                sock.PendingError = LogonErrorCode.LOGIN_BADVERSION
            End If
            Dim _id As String = data.acc
            Dim _pass As String = m_Database.GetItem("id='" & _id & "'", "password")
            If _pass Is Nothing Then sock.PendingError = LogonErrorCode.LOGIN_UNKNOWN_ACCOUNT
            Dim TempHash As SHA1
            sendpacket.cmd = LogonOpcode.CMD_AUTH_LOGON_CHALLENGE
            'If sock.PendingError <> LogonErrorCode.LOGIN_OK Then sock.PendingError = LogonErrorCode.LOGIN_OK
            'If sendpacket.error <> LogonErrorCode.LOGIN_OK Then sendpacket.error = LogonErrorCode.LOGIN_OK
            sendpacket.unk1 = 0
            sendpacket.N_len = 32
            sendpacket.N = New BigInteger("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7")
            sock.N = New BigInteger(sendpacket.N)
            sendpacket.salt = tmp.genRandom(8 * 32).getBytes
            sock.Salt = New BigInteger(sendpacket.salt)
            sendpacket.g_len = 1
            sendpacket.g = 7
            TempHash = SHA1.Create
            Dim temp As String = _id & ":" & _pass
            sock.Username = _id.ToUpper
            TempHash.TransformFinalBlock(Helper.strtobytes(temp.ToUpper), 0, temp.Length)
            temphashb = TempHash.Hash
            temphashb = Helper.CombinBytes(sendpacket.salt, temphashb)
            TempHash = SHA1.Create
            TempHash.TransformFinalBlock(temphashb, 0, temphashb.Length)
            temphashb = TempHash.Hash
            Array.Reverse(temphashb)
            x = New BigInteger(temphashb)
            temphashb = sendpacket.N.getBytes
            Array.Reverse(temphashb)
            tmp = New BigInteger(temphashb)
            Dim g As New BigInteger(sendpacket.g), v As BigInteger, k As New BigInteger(3)
            v = g.modPow(x, tmp)
            sock.v = New BigInteger(v)
            Dim tmp2 As BigInteger, b As BigInteger
            b = BigInteger.genRandom(20 * 8)
            sock.pb = New BigInteger(b)
            Dim bb() As Byte = b.getBytes
            Array.Reverse(bb)
            b = New BigInteger(bb)
            tmp2 = BigInteger.op_Multiply(k, v)
            tmp = g.modPow(b, tmp)
            tmp = BigInteger.op_Addition(tmp2, tmp)
            tmp = BigInteger.op_Modulus(tmp, New BigInteger(temphashb))
            temphashb = tmp.getBytes
            Array.Reverse(temphashb)
            sock.B = New BigInteger(temphashb)
            sendpacket.B = temphashb
            sendpacket.unk2 = BigInteger.genRandom(8 * 16).getBytes
            Dim datas() As Byte, br As New BinReader
            ReDim temphashb(0)
            temphashb(0) = sendpacket.cmd
            datas = temphashb
            temphashb(0) = sendpacket.error
            datas = Helper.CombinBytes(datas, temphashb)
            temphashb(0) = sendpacket.unk1
            datas = Helper.CombinBytes(datas, temphashb)
            datas = Helper.CombinBytes(datas, sendpacket.B)
            temphashb(0) = sendpacket.g_len
            datas = Helper.CombinBytes(datas, temphashb)
            temphashb(0) = sendpacket.g
            datas = Helper.CombinBytes(datas, temphashb)
            temphashb(0) = sendpacket.N_len
            datas = Helper.CombinBytes(datas, temphashb)
            datas = Helper.CombinBytes(datas, sendpacket.N.getBytes)
            datas = Helper.CombinBytes(datas, sendpacket.salt)
            datas = Helper.CombinBytes(datas, sendpacket.unk2)
            Dim packets As New Packet.PacketSendManager.Packet
            packets.sock = sock.CSocket
            packets.data = datas
            ConMsg("Challenge运算时间:" & GetTickCount - tick)
            m_SendMgr.AddPacket(packets)
            Return True                           
            
                  
相关报道: