Nebula3工具库, 包含一些工具类, 容器类, 还有一个强大的String类.
下面分别来看一下有哪些东东:
Array< TYPE >
动态数组, 类似std::vector, 自带了排序方法和二分查找
Atom< TYPE >
对于持续存在对象的共享引用. 简单得来说, 就是一个生命周期很长的对象的智能指针, Atom<String>是最常用的, 作为常量字符串的封装.
Blob
大块内存空间的封装, 可以比较, 复制, 计算Hash值
CmdLineArgs
通用的命令行参数解析器, 格式: cmd arg0[=]value0 arg1[=]value1 arg2[=]value2
Crc
计算一段内存的CRC值
Dictionary< KEYTYPE, VALUETYPE >
词典类, 用于存储映射. 类似于std::map. 取元素的时间复杂度为O(log n). 内部是一个排序的Array实现的. 注意它只是在需要排序时才排, 所以加入元素很快, 而第一次的搜索会慢一些.
FixedArray< TYPE >
定长数组, 一维
FixedTable< TYPE >
表格, 定长二维数组
FourCC
四字符编码, 相当于一个uint, 可以做为ID, 具有可读性. 前面的工厂方法就用到了. (第一次见单引号里写多个字符@_@, 如uint = ‘ABCD’;)
Guid
全局统一标识符(GUID), 每台机器在不同时间生成的都不一样, 可以说是唯一性的.
HashTable< KEYTYPE, VALUETYPE >
跟Dictionary很像, 不过内部是用哈希表实现的, 搜索时间更快(O(1)), 内存占用要大一些. 相当于stdext::hash_map
做KEY的类必需实现这个方法: IndexT HashCode() const
KeyValuePair< KEYTYPE, VALUETYPE >
相当于std::pair
List< TYPE >
双向链表, 相当于std::list
Proxy< TYPE >
相当于带引用计数的智能指针, 普通类也可以用它进行包装, 而不用继承Core::RefCounted
Queue< TYPE >
队列, 相当于std::queue
SimpleTree< VALUETYPE >
简单的树型结构, 结构存储在Array中
Stack< TYPE >
堆栈, 相当于std::stack
String
字符串类, 相当于std::string, 但是功能强大得多. 提供了与其它Nebula数据类型的转换方法, 还有文件名操作函数.
Variant
通用数据类型, 相当于COM中的VARIANT
关于各个类的详细用法,可以参考testfoundation_win32工程.

核心子系统

核心库(Core namespace)实现了这些特性:

  • 一个实现了引用计数的RefCounted基类
  • 一个运行时类型信息系统(RTTI)
  • 一个模板智能指针, 用于处理RefCounted对象的生命周期
  • 一个由类名创建C++对象实例的工厂机制
  • 一个中央Server对象用于建立基本的Nebula3运行环境

对象模型

Nebula3在C++对象模型的基础之上实现了下面这些新特性:

  • 基于引用计数和智能指针的生命周期管理
  • 基于类名或四字符编码的对象创建
  • 一个运行时类型信息系统

实现一个新的Nebula3类

当实现一个新的类时首先要考虑它是一个传统的C++类还是要从Core::RefCounted继承. 以下几点可以帮你找到答案:

  • 如果这个类需要使用Nebula3的扩展对象特性, 如引用计数, RTTI等, 则它必须从Core::RefCounted继承.
  • 如果这个类是一个典型的小工具类, 如动态数组, 数学向量, 或其它相似的东西, 那么它从Core::RefCounted 继承也没有什么意义.

从Core::RefCounted类继承有一些限制:

  • RefCounted派生类不应该在栈上创建对象, 因为栈对象的生命周期是由C++来管理的(他们会在离开当前上下文时被销毁, 从而绕过了Nebula3的引用计数生命周期 管理)
  • RefCounted的派生类只有一个默认的构造函数.
  • RefCounted的派生类必须有一个虚析构函数.
  • RefCounted的派生类不能进行拷贝, 因为这样会造成引用计数机制混乱.

要使用Nebula3的对象模型特性, 除了需要从Core::RefCounted继承外, 还需要在头文件新类的声明中进行额外的标注:

一个标准的RefCounted派生类一般这样声明:

1: namespace MyNamespace

2: {

3: class MyClass : public Core::RefCounted

4: {

5: DeclareClass(MyClass);

6: public:

7: /// constructor

8:     MyClass();

9: /// destructor

10: virtual ~MyClass();

11: ...

12: };

13: RegisterClass(MyClass);

注意DeclareClass()宏, 构造函数, 析构函数还有类外面的RegisterClass()宏. DeclareClass()宏加入了RTTI和工厂机制所需的最小代价的信息, 它隐藏了Nebula3的对象模型, 希望可以在不影响已有类的基础进上进行内部机制的变更. RegisterClass()宏是可选的, 它把当前类在中央工厂进行注册. 如果你知道这个类永远不会由类名或四字符编码进行创建, 这个宏可以省略.

在这个类的.cpp文件里需要包含Nebula3特有的信息:

1: namespace MyNamespace

2: {

3: ImplementClass(MyNamespace::MyClass, 'MYCL', Core::RefCounted);

4:

5: }

ImplementClass()宏注册类的RTTI机制, 第一个参数描述了类的名字(注意命名空间必须包含). 第二个参数是类的四字符编码, 它必须是所有类中唯一的(如果有重复, 你会在启动程序时得到一个错误提示). 第三个参数是父类的名字, 用于RTTI系统去构造类的关系树.

引用计数和智能指针

Nebula3使用传统的引用计数来管理对象的生命周期. 一个模板智能指针类Ptr<>对程序员隐藏了引用计数的实现细节. 一般来说, 应该一直使用智能指针指向RefCounted的派生对象, 除非你能肯定在给出的代码块中这个对象的引用计数不会发生变化.

智能指针相对于一般指针有很多好处:

  • 访问一个空指针会给你一个断言警告而不是一个内存错误
  • 你不需要对引用计数的对象调用AddRef()或Release() (事实上如果你调了, 会了发生严重的错误)
  • 智能指针可以在容器类里良好地工作, 一个智能指针的数组会消除所有的一般指针需要的生命周期管理, 你永远不需要考虑去释放指针所指针的对象, 数组包含的像是真正的C++对象一样
  • 用智能指针不需要考虑指针的所属, 不需要为谁delete对象而烦恼

智能指针也有一些缺点:

  • 性能: 拷贝和赋值会引起对象的引用计数的变化, 解除引用会引起指针的断言检查. 这导致的性能消耗一般是可以忽略的, 但是你最好保证它不在内部循环中发生.
  • 应该销毁的对象还存在: 因为智能指针管理的对象只有在最后一个引用放弃时才会销毁, 这样会使对象存在超过预订的时间. 这经常会导致一个BUG的产生. 不过引用计数泄露(程序退出时还仍然存在的对象)时Nebula3会提醒你.

创建Nebula3对象

从Core::RefCounted继承的类可以通过3种不同的方式进行创建:

直接通过静态的Create方法:

1: Ptr<MyClass> myObj = MyClass::Create();

静态的Create()方法是之前提到的DeclareClass()宏加入的, 相对于new操作符来说, 它并没有多做什么. 注意正确使用智能指针来保存新建的对象.

另一种创建方式是通过类名:

1: using namespace Core;

2: Ptr<MyClass> myObj = (MyClass*)Factory::Instance()->Create("MyNamespace::MyClass");

当你在运行时通过类名来创建十分有用, 特别是对象的反序列化和脚本接口的使用. 注意类型转换是必须的, 因为工厂的Creat()方法返回的是RefCounted指针.

由类名创建的变种是根据四字符编码进行创建:

1: using namespace Core;

2: using namespace Util;

3: Ptr<MyClass> myObj = (MyClass*) Factory::Instance()->Create(FourCC('MYCL'));

这个方法看上去没有那个直观, 但是它比类名创建快得多. 并且四字符编码比类名占用的空间更少, 这更利于对象写入二进制流或从中读取.

运行时类型信息系统

Nebula3的RTTI系统可以让你在运行时访问对象的类型, 检查一个对象是不是某个类的实例, 或者某个派生类的实例. 你也可以直接获得一个对象的类名和四字符编码. 所有这些功能是由DeclareClass() 和 ImplementClass() 宏在背后实现的.

这时有示例程序:

1:     using namespace Util;

2:     using namespace Core;

3:

4: // check whether an object is instance of a specific class

5: if (myObj->IsInstanceOf(MyClass::RTTI))

6: {

7: // it's a MyClass object

8: }

9:

10: // check whether an object is instance of a derived class

11: if (myObj->IsA(RefCounted::RTTI))

12: {

13: // it's a RefCounted instance or some RefCounted-derived instance

14: }

15:

16: // get the class name of my object, this yields "MyNamespace::MyClass"

17: const String& className = myObj->GetClassName();

18:

19: // get the fourcc class identifier of my object, this yields 'MYCL'

20: const FourCC& fourcc = myObj->GetClassFourCC();

你也可以向中央工厂查询一个类是否已经注册:

1:     using namespace Core;

2:

3: // check if a class has been registered by class name

4: if (Factory::Instance()->ClassExists("MyNamespace::MyClass"))

5: {

6: // yep, the class exists

7: }

8:

9: // check if a class has been registered by class fourcc code

10: if (Factory::Instance()->ClassExists(FourCC('MYCL')))

11: {

12: // yep, the class exists

13: }

Nebula3单件

很多Nebula3的核心对象都是单件, 就是只存在一个实例, 并且所有其它对象都知道它.

你可以通过静态方法Instance()来访问单件, 它返回唯一实例的一个指针. 返回的指针保证是合法的. 如果在调用Instance()方法时对象实例不存在, 一个断点会被抛出:

1: // obtain a pointer to the Core::Server singleton

2:     Ptr<Core::Server> coreServer = Core::Server::Instance();

你也可以检查单件是否存在:

1: // does the Core::Server object exist?

2: if (Core::Server::HasInstance())

3: {

4: // yep, the core server exists

5: }

Nebula3提供了一些辅助的宏来实现单件:

1: // declare a singleton class

2: class MySingletonClass : public Core::RefCounted

3: {

4: DeclareClass(MySingletonClass);

5: DeclareSingleton(MySingletonClass);

6: public:

7: /// constructor

8:     MySingletonClass();

9: /// destructor

10: virtual ~MySingletonClass();

11: ...

12: };

13:

14: // implement the singleton class

15: ImplementClass(MyNamespace::MySingletonClass, 'MYSC', Core::RefCounted);

16: ImplementSingleton(MyNamespace::MySingletonClass);

17:

18: //------------------------------------------------------------------------------

19: /**

20:     Implements the Singleton constructor.

21: */

22: MySingletonClass::MySingletonClass()

23: {

24: ConstructSingleton;

25: }

26:

27: //------------------------------------------------------------------------------

28: /**

29:     Implements the Singleton destructor.

30: */

31: MySingletonClass:~MySingletonClass()

32: {

33: DestructSingleton;

34: }

DeclareSingleton()和ImplementSingleton()宏跟DeclareClass()和ImplementClass()宏差不多.它们在类中添加了一些静态方法(也就是Instance()和HasInstance()). 类的构造函数和析构函数必须包含ConstructSingletonDestructSingleton宏. ContructSingleton初始化了一个私有的单件指针并保证没有其它的类实例存在(如果不是, 会抛出断言). DestructSingleton让私有的单件指针无效化.

单件的访问默认是只有本地线程. 这意味着在一个线程中创建的单件无法被其他线程访问. 这使得”并行Nebula”大大简化了多线程编程. “并行Nebula”的基本思想是, 一个典型的Nebula3应用程序包含一些”Fat线程”, 每一个Fat线程都是运行在一个单独的CPU核心上. Fat线程可以用于实现异步IO, 渲染, 物理等等. 每一个Fat线程都初始化了它们自己的Nebula3运行环境, 它们执行特性任务所需的最少依赖. 这基本上消除了大部分Nebula3代码的同步问题, 并且把线程相关的代码集中到一个明确定义的代码区域中. “并行Nebula”的另一个好处就是, 程序员在多线程环境中编程时不需要关心太多. 大多数Nebula3代码看起来就像单线程代码一样, 但是它们却运行在各自的Fat线程中.

性能与内存占用的考虑

Nebula3核心层的一个设计目标就是减少底层代码的内存占用, 来更好的适应微型平台, 像手持设备. 这里有一些已经完成的目标:

  • RefCounted 类在每个实例中只增加了4byte用于引用计数.
  • RTTI机制在开头增加了30 到 60 byte, 但是这是对于每个类来说的, 而是不是每个实例.
  • 一个智能指针仅仅4 byte, 就像普通指针一样.
  • 一些监控结构只会在debug模型下创建, 特别是用来检测引擎计数泄露的RefCountedList.

这里一些用三种不种的创建方法创建一百万个RefCounted 对象所需的时间信息. 这些时间信息是在台Intel Pentium 800 MHz的笔记本上得出的.  

  • Create(): 0.29 seconds
  • FourCC: 0.65 seconds
  • 类名: 1.45 seconds

前言

目前这个引擎只是一个预览版, 很多功能仍在开发当中

硬件需求: 显卡支持ShaderModel3.0

作者Blog: http://flohofwoe.blogspot.com

用到的开源工程

为了避免版本之间的不兼容, 已经在SDK中包含

体系结构一览

  • Nebula3 分成三层, 每一层都是建立在另一层之上的:
    • 基础层: 最底层, 提供了一个图形和音频之下基本的平台抽象. 基础层可以用作任意类型的程序开发平台, 而不仅仅是实时3D程序.
    • 渲染层: 这是中间层, 它在基础层之上另加了许多特性, 像3D渲染, 音频, 物理和场景管理等.
    • 应用程序层: 这是最高的一层, 提供了一个完整的游戏框架, 这使得开发人员可以集中精力在游戏逻辑上, 而不用对关心各种细节实现.
  • Nebula3 会跟Mangalore 合为一个整体, Mangalore的各种子系统会集成到Nebula3的适合它们的层中去.
  • Nebula3 比 Nebula2更趋向于使用C++.
  • Nebula3通过引用计数和智能指针实现了对象生命周期的管理.
  • Nebula3的新对象模型使用一个4 byte的基类来代替Nebula2中70+ bytes的.
  • RTTI更高效, 更易用.
  • Nebula3仍然不使用C++异常, RTTI和STL(所有这些不是降低性能就是降低便携性).
  • 根据类名来创建对象更快更易用.
  • Nebula3 避免使用C Lib, 去除了附加的代码层.
  • Nebula3 使用LUA 代替TCL作为标准的脚本语言(当然也可以增加其它脚本语言的支持)

基础层

  • App 包含各种Application类
  • Attr 属性注册
  • Core                Nebula3的对象模型
  • Debug 程序内存转储和基于HTTP的调试信息(这个真是个好东西, 第一次听说)
  • Http 实现了HTTP服务器和客户端, 这样可以在浏览器中实时查看调试信息
  • IO 输入输出系统, 很方便
  • Math 基于C++的数学库, 没什么特别的
  • Memory 实现了定制的内存分配器
  • Messaging 消息定义
  • Net 最基本的基于TCP协议的C/S架构, 更多的功能会在高层的网络扩展中给出
  • Scripting 脚本系统
  • System 硬件平台和特定操作系统相关的一些特性
  • Threading 多线程
  • Timing 时间/定时器管理
  • Util 工具库, 包括各种容器, 数据结构, 还有一个强大的string类(抛弃STL吧!)

渲染层

  • CoreGraphics 图形库核心类
  • Frame 基于帧的处理, 如RenderTarget, PostProcess等
  • Graphics 一些实体类型定义, 如Camera之类
  • Input 输入设备, 就是鼠标键盘手柄…..
  • Lighting 光照处理, 包括阴影算法(竟然要SM3.0@_@)
  • Models 骨骼模型&粒子系统
  • RenderUtil 目前就只有一个Maya的摄像机…..
  • Resources 资源管理器

应用程序层

application_win32工程就是一个简单的游戏, 框架, 但是已经包含了许多特性, 如果物理, 数据库等. 不过好像还缺少GUI系统.

插件

目前有三个, 基于ODE的物理引擎, 基于SQLite的本地数据库, 基于nebula2模型的骨骼模型系统

编译

没什么好说的, 打开.sln直接编译就成了, 没见过这么简单的开源工程^_^

 

就以这个程序做为测试程序的模板吧

 1: #include "stdneb.h"

2: #include “core/debug.h”

3: #include “core/coreserver.h”

4: #include “core/sysfunc.h”

5:

6: using namespace Core;

7:

8: void

9: __cdecl main()

10: {

11: Ptr<CoreServer> coreServer = CoreServer::Create();

12: coreServer->SetAppName(“Nebula3 Hello World!”);

13: coreServer->Open();

14:

15: n_printf(“Hello World\n”);

16: system(“pause”);

17:

18: coreServer->Close();

19: coreServer = NULL;

20:

21: Core::SysFunc::Exit(0);

22: }

最近拿到SpeedTree资料,开始学习,并用到项目里去.

images/spt0.jpg

1.  该插件的特点:
api无关。它本身只是数据结构和逻辑架构,没有任何渲染语句子,因此为了把它应用到自己的引擎里,需要为之添加渲染相关的语句。而根据sdk的讲解,推荐用户为之搭建中间架构,用来联系SPEEDTREE与自己的引擎。这样做起码有两点好处,搭建的中间架构(也推荐别加任何api相关的语句),因此,即使你以后换了api(譬如从gl换成dx),中间架构还是可以继续沿用的。还有一个好处就是,当speedtree更新版本的时候,你也无须修改你的引擎,而只需要修改相对简单而且稳定的中间架构。

images/spt1.jpg

2.  该插件的具体特性:
注意,下面具体特性分析都是基于SDK里一个叫“DirectX9”的例子进行的,在这个例子里,它给出了最基本的使用方法,同时也向用户展示了它的基本特性。
A.  树的基本渲染
通过大场景的测试,DP的个数大致是树木棵数的两到三倍。详细分析下,发现
它一棵树分三部分绘制:树干和大树枝(branches),小树枝(fronds),树叶(leaves)
Branches:使用模型来绘制
Fronds:使用两个十字交叉的面模拟小树枝,为了节省三角形。
Leaves:使用billboard方式绘制,这样就能产生视觉效果比较好的叶子了。
它这样划分是出于以下三方面的考虑:这几部分的渲染状态不一样,动画的状态不一样,做LOD的时候也不一样。具体看下面的介绍。
B.  树的阴影系统
它包括两方面的阴影。首先是树干上的阴影。其次是整棵树在地面的投影。
树干的自阴影(self shadow)是预先生成的,至于生成的算法,可能是可以根据可穿透的光线跟踪,也可能是结合shadow map的逐象素地生成光照贴图(把树干的面都展开后,在对应的地方画上阴影).有了该光照贴图,那渲染树干的时候就可以跟树干本身的纹理进行混合产生比较真实的效果。
而整棵树在地面的透影子,则是使用一个矩形画出来的,阴影贴图也是预先生成好。渲染的时候浮在地面。
C.  树的动画
树的三部分的动画状态都是不一样的。这对优化有极其重要的作用。风小的时候,或是树离眼睛比较远的时候,可以不动树枝,而只是动树叶。而具体他们是怎么动的:
树叶的动画:就是一个billboard的来回平移以及他本身绕视坐标系统Z轴的转动。
树枝的动画:通过它引擎本身计算出来的矩阵进行动画。
而至于它具体怎么渲染动画的,它提供了基于CPU和GPU的方法。
基于CPU的方法是:创建顶点缓冲的时候, 使用D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY标记(这种方法能提高CPU修改和更新该缓冲的速度),渲染的时候实时更新顶点位置。
基于GPU的方法是:通过自定义的顶点shader程序进行,更新动画的时候,向shader传递常量数组。
D.  树的光照
它可以打开和关闭实时光照,对于实时光照,树干部分又分两种情况,对于没有法线贴图的树干,使用per-vertex的光照。而对于有法线贴图的,则使用per-pixcel光照。至于给不给树干渲染法线贴图,则根据具体的程序决定。
而对于树叶的渲染,因为它是一个billboard,因此也无法通过其法线来计算光照。它其实是根据这个 billboard的位置来确定其亮度的。通过把整棵树当成一个球来分析,而每个billboard的位置就相当于是球上的一点,结合光的方向,计算出该点的亮度。
E.  LOD的特点
其强大的LOD系统,为实现大规模的场景提供了有力的支持。这里的LOD分三方面:顶点的LOD,纹理的LOD,动画的LOD。
(1)  顶点的LOD:首先是针对树干,因为这里的树干是实实在在的模型。至于树干的建立,它里面是采用贝塞尔曲线来描述整个mesh的,贝塞尔曲线的描述方式无疑给即时高效率的LOD计算提供了可行性。同时这还针对树枝,远了之后,小树枝就不渲染了。到了一定的距离的时候,整棵树其实就变成一个billboard了。
(2)  纹理的LOD:树干上在最高精度的时候会有三套纹理:基本纹理,光照贴图,法线贴图。随着LOD的进行,可以依次减去法线贴图,光照贴图,最后是本身贴图,最后只为树干渲染一种颜色。
(3)  动画的LOD:现在有三种动画,大树枝(模型)的动画,小树枝(两个交叉面)的动画,以及树叶的动画。随着LOD的进行,依次去掉大树杆的动画,小树杆的动画,最后是树叶的动画。这也是符合视觉效果的。
F.  文件系统
用场景来分析的话,一个场景是.stf文件(Speed Tree Forest).该文件描述了每棵树的相关属性。而一棵树是通过一个.spt(Speed Tree)文件来描述的.用文本编辑器打开,就能看到里面记录了该树的所有信息。而该插件为此开发了配套了树木编辑器材。使用该编辑器,打开.spt文件之后,就可以对该树进行浏览以及编辑。
3.Speedtree使用实践
它提供给用户的一个最主要的类就是CSpeedTreeRT.这是一个speedtree对外界的接口,从SpeedTreeRT.h中可以看到,这个类其实是包括了该插件的核心类.因此,我们在使用该插件的时候,其实全都是通过这个接口。
譬如CSpeedTreeRT::SetCamera(eye, viewDir),通知它内部现在的摄像机的信息,然后它内部就根据这些信息计算出正确的billboard.
而如何加载一棵树呢?使用CSpeedTreeRT::LoadTree(const char *treefile);输入一个”.spt”文件,然后我们设置光照和风效果的方法如CSpeedTreeRT::SetBranchWindMethod,SetFrondWindMethod,SetBranchLightingMethod,SetLeafLightingMethod, SetLodLimits等,接着执行CSpeedTreeRT::Compute(),然后它里面就开始进行黑盒处理,最后我们就可以获取其几何数据(CspeedTreeRT::GetGeometry)进行渲染。获取之前还可以手动去设置LOD级别CSpeedTreeRT::SetLodLevel,然后你获取到的就是经过LOD处理的几何数据。
不过有一点需要要注意的是,speedtree里面用的是右手坐标系(尽管它说可以通过define Y_UP来改变坐标系统,但我没发现define改了之后有什么变化,很奇怪)。笔者开始的时候完全没注意到这点,发现搬到自己的架构后,树全都是横着的。当时死活发现不了问题,就去旋转每棵树。然后又发现那些树叶也无法正常地旋转成billboard,又查了很久。后来终于发现,是因为speedtree内部使用右手坐标系进行计算。而我的架构是使用左手,这样一来,连传给speedtree camera的数据都要修改了, CSpeedTreeRT::SetCamera(eye, viewDir),其中的eye,eyeDir,都得经过变换再传进去:
float3 viewDir=pCamera->GetViewDir();
float3 eye=pCamera->GetEye();
  float afDirection[3];
  afDirection[0] = viewDir.x;
  afDirection[2] = viewDir.y;
  afDirection[1] = -viewDir.z;
  CSpeedTreeRT::SetCamera(eye, afDirection);
4.把speedtree加到自己的引擎中去
以上所说的CSpeedTreeRT接口,笔者在使用的时候都是让一个CSpeedTreeRT对象汇聚到自己设计的一个tree类里。通过这种方式来封装speedtree,搭建中间架构。CSpeedTreeRT这接口也许多静态函数,譬如SetCamera,参照它的DEMO,
直接“CSpeedTreeRT::SetCamera(eye, eyeDir);”但要实现完美地跟自己的引擎相结合,也并不是一件容易的事情。主要是,自己的引擎本来就有一套完整的渲染系统,LOD系统,动画系统,而且跟speedtree的方式也不一样。一个极端的做法就是,对于SpeedTreeRT,屏蔽其实时计算,而是根据自己引擎的系统计算,这样的话, 其实是只利用了SpeedTree的数据结果了。而另外一个极端就是,不管 speedtree和自己引擎的关系,只保留简单的耦合,各自使用各自的系统,只是让他们的渲染行为(LOD,光照效果等)保持一致性。至于更好的办法,笔者也是在研讨中,我非常希望能跟读者进行探讨,这也是笔者写本笔记的动机之一。

http_imgload

Game组成员超过Hack组,不过Game的大部分人都不认识,有的甚至没有联系过。
我的好友中还有部分Hack没有联系上,至少应该还有20名左右吧,实际上加起来应该还比Game组的多。
我的好友中大部分应该是搞过C++或WinSDK编程的,至少是学过编程。
怀念从2000年至今的编程学习生涯,这是第三个QQ了,
最早一个QQ上的人大部分现在都应该是大牛了吧,可惜偶还是小弟,悲哀之。
这么多年过去了,发现自己依旧是那么无知无畏。
人一生所寻找的也许就是脚下的路吧,这样的人生才会意义。
这样才过的踏实一点,去年跟一朋友来到福州,来这里做游戏,
等到实际工作中才发现游戏不比传统软件行业,
这里的人比传统软件行业时的人更有想法,但大部分都不切换实际,甚至弱智。
为一点所谓的利益搞来搞去,其实真正的利益在BOSS那里,因为BOSS付出了。
不付出是得不到回报的,搞来搞去只是在牺牲自己的脑细胞。
不坚持原则,不追求完美,不追求质量,不切合实际,不明确目标。
完美的程序员至少是个政治家,虽然我不完美,但我是政治家,不要和我玩政治。
这里没有失败,因为我们只是为BOSS服务的。
损害别人并不能得到什么,因为每个人都选择的权利,因为和谁玩牌都一样。
输掉的只是大家的时间。

The Release builds include optimizaed code, but with the NiMemory system, NiMetrics, and release mode logging enabled. The Shipping builds do not have these systems enabled.
ship 和 release 工程设置基本相同,但没有NiMemory、NiMetrics、release mode logging.更像是非常稳定之后的版本.

不可否认

选择做程序员 源于真正意义上的兴趣和喜欢

在做程序员的这几年 我的生活也在一步步的提升

但同样 不可否认

在我个人眼里

就像选择其他种类的工作一样

只是一份职业 一种在社会上存身立足的手段

现在呢

又在想 自己以前想过 大家也时不时的都会想 的问题

那就是 程序员的人生 将如何规划

在校时 以及 工作后 都曾这么的想过:

做一个IT从业人员 做一个程序员  

做上个一二年

然后 向上提升下 做一个项目经理 什么的

再然后

或许 开一家自己的小软件公司

或许 做一名软件顾问

再或者 开一个网站 开发一个自己的小软件

或者 真的不行的 利用做程序员这几年的积蓄

做一些小买卖 转行 另谋生计

总之 感觉做程序开发将是人生的一个过渡

可若真的按这种思路一过渡

就将是用我人生的青春年华大好时光的五六年 或者更多时间

遗憾的是

至今 对这种付出后的收获

我却没有把握

曾听有人言(这一定是国人说的):

一年管理成富翁

三年市场路路通

十年技术一场空

这话每每想来 心里都不免有些低落

低落的不是现在

而是在现在看来 自己一两年或几年后的生活

没有着落 没有依靠

而那时的自己

或许做了项目经理

薪资在才做程序员的后辈们看来 已很是不低

但却可能远远不够日常生活的进一步开支

因为我们每个人每时每刻都有让生活越来越好的念头和目标

我们不想自己上去了 又下去了

不想自己 由前几年刚毕业的  蓝领代码工人

好不容易辛苦努力才做到今天的  白领项目经理

接着却因年事问题 薪资待遇问题 而沦为 房奴 车奴

我们不回避 人生将由

1.幼年-童年-少年-青年

2.成年-壮年

3.老年

这三段的划分 也不忌讳自己真的有一点老不中用了

但我们不能容忍自己在

从三十岁到五十岁

这段时间里 碌碌无为

而现在

我们站在目前自己做程序员的角度

去看三五年后的而立之年

我们心里没底

我们站在而立之年的程序员的角度

去看自己30-50的人生成熟和收获的黄金时期

我们更多的可能看到的是灰暗和苦涩

那么

早知如此 何必当初

想问大家 也是在问自己 一句

程序员的人生 该将如何规划?

(

希望成功的前辈们 能多多赐教

也希望有同样思考的同辈们

能说说自己好的想法和规划

或许

这个问题

不单单是属于做程序员工作的同行们

也可能是属于所有现在 没有自己的事业 正在工作着的 各行各业的同志们

我思 故我在

没有对明天的思考 明天的我 也许就没有美好的未来

也许换种环境更好

比如出国

至少不会有职业歧视

至少技术与业务一视同仁

至少会认为每个人的工作同样重要

)

­

*注:转载,部分修改.

由于项目的原由最近接触了很多优秀的项目,其中包括HTML排版引擎,以下对其做下简单的介绍和比较.

现在浏览器的内核引擎,基本上是三分天下:

  • Trident: IE 以Trident 作为内核引擎。
  • Gecko: Firefox 是基于 Gecko 开发。
  • WebKit: Safari, Google Chrome 基于 Webkit 开发。
  1. Trident

    Trident (又称为MSHTML),是微软的窗口操作系统(Windows)搭载的网页浏览器—Internet Explorer的排版引擎的名称,它的第一个版本随着1997年10月Internet Explorer第四版释出,之后不断的加入新的技术并随着新版本的Internet Explorer释出。在未来最新的Internet Explorer第七版中,微软将对Trident排版引擎做了的重大的变动,除了加入新的技术之外,并增加对网页标准的支持。尽管这些变动已经在相当大的程度上落后了其它的排版引擎,如Gecko、WebCore、KHTML及Presto。

      Trident引擎被设计成一个软件组件(模块),使得其它软件开发人员很容易的将网页浏览的功能加到他们自行开发的应用程序里。微软提出了一个称为组件对象模型(COM)的软件接口架构。供其它支持的组件对象模型开发环境的应用程序(如:C++及.NET)存取及编辑网页。例如,由C++所撰写的程序可以加入浏览器控件里,并透过Trident引擎存取当前显示在浏览器上的网页内容及网页的各种元素的值,从浏览器控件触发的事件亦可被程序撷取并进行处理。Trident引擎所提供的所有函式库可以透过与 mshtml.dll这个档案的连结而达成撰写程序时所需要的功能。

      除此之外,微软还有另一个网页浏览器排版引擎,称为Tasman,它是使用在「Internet Explorer for Mac」的排版引擎。相较于Trident,Tasman引擎对网页标准有较佳的支持。与普遍的看法相反的是,微软已经停止了麦金塔计算机版本的 Internet Explorer的开发,但Tasman的开发仍旧持续, 新版本的Tasman引擎仍被应用在一些微软产品上,如:麦金塔计算机版本的Microsoft Office。
      使用Trident引擎的浏览器有很多,比如Maxthon,腾讯TT,MyIE等等,但Trident只能应用于Windows平台.

  2. Gecko
    Gecko是套开放源代码的、以C++编写的网页排版引擎。目前为Mozilla家族网页浏览器以及Netscape 6以后版本浏览器所使用。这软件原本是由网景通讯公司开发的,现在则由Mozilla基金会维护。

    这套排版引擎提供了一个丰富的程序界面以供互联网相关的应用程式使用,例如网页浏览器、HTML编辑器、客户端/服务器等等。虽然最初的主要对象是Mozilla的衍生产品,如Netscape和Mozilla Firefox,现在已有很多其他软件现在利用这个排版引擎。Gecko是跨平台的,能在Microsoft Windows、Linux和Mac OS X等主要操作系统上运行。

    Gecko是最流行的排版引擎之一,其流行程度仅次于Trident.
    使用Gecko引擎的浏览器有Firefox, 网景(6至9), SeaMonkey, Camino, Mozilla, Flock, Galeon, K-Meleon, Minimo, Sleipnir, Songbird , XeroBank.

  3. WebKit
    WebKit是Mac OS X v10.3及以上版本所包含的软件框架(对v10.2.7及以上版本也可通过软件更新获取)。 同时,WebKit也是Mac OS X的Safari网页浏览器的基础。WebKit是一个开源项目,包含了来自KDE项目和苹果公司的一些组件。
    目前使用WebKit 引擎的浏览器主要有:Safari(apple出品),Midori,chrome(google出品)等。

    WebKit拥有清晰的源码结构、极快的渲染速度。

现代游戏已经不能没有声音,所以音频引擎成为游戏引擎中不可缺少的一部分.这是一篇介绍现代音频引擎的文章(http://hard.zol.com.cn/labs/2003/0520/60986.shtml).FMOD音频引擎(http://www.fmod.org)是一个非常不错的音频引擎,其使用也比较简单,下面做一些简单介绍:
一.基本准备
它是免费的,你可以从它们的主站上下载API等文件.之后,你需要添加头文件和库文件,如下(C/C++):

  • fmodvc.lib 用于 Microsoft Visual C++ 和 Codewarrior
  • fmodbc.lib 用于 Borland
  • fmodwc.lib 用于 Watcom
  • fmodcc.lib 用于 LCC-Win32
  • libfmod.a 用于 MingW and CygWin
  • fmod-3-7.lib 用于 GCC
    (参考:http://www.gamedev.net/reference/articles/article2098.asp
    之后,只要添加fmod.h头文件后就可以使用了.
    二.开始使用
    1.初始化
    开始播放声音前,需要进行初始化,很简单:
    FSOUND_Init (44100, 32, 0);
    第一个参数是输出HZ,第二是最大软件信道数可以不管也不会增加CPU负担,第三个参数可以设置一些标志可以不设置则赋值为0.
    2.基本常识
    FMOD将音频分为声音(sound)和音乐(music)两种.前者如:.MOD, .S3M, .XM, .IT, .MID, .RMI, .SGT or .FSB
    等,后者如: .WAV, .MP2, .MP3, .OGG or .RAW等.二者使用不同的函数处理.都可以通过采样后流的方式来处理.不过小文件一般通过采样方式,它可以多次播放但占用内存.大文件通过流方式,减少内存消耗.
    3.播放音乐
    首先定义一个FMUSIC_MODULE类型变量来作为文件句柄.然后就可以通过FMUSIC API来实现,如:
    装入文件:
    handle=FMUSIC_LoadSong("YourFileName");
    FMUSIC_PlaySong(handle);
    音量控制:FMUSIC_SetMasterVolume (handle, 255);后面的参数在0~255之间,值越大声音越大.
    暂停播放:FMUSIC_SetPaused (handle, true);
    重开始:FMUSIC_SetPaused (handle, false);
    循环播放:FMUSIC_SetLooping (handle, true);
    停止播放:FMUSIC_StopSong (handle);
    释放音频内存:FMUSIC_FreeSong (handle);
    下面是一个命令模式下的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FMUSIC_MODULE* handle;
    int main ()
    {
       // 初始化
       FSOUND_Init (44100, 32, 0);
       // 装如
       handle=FMUSIC_LoadSong ("canyon.mid");
       // 只播放一次
       // 播放midi文件时请关闭循环播放
        FMUSIC_SetLooping (handle, false);
       //播放
       FMUSIC_PlaySong (handle);
      // 按任一键结束
       while (!_kbhit())
       {
       }
       //释放
       FMUSIC_FreeSong (handle);
       FSOUND_Close();
    }
    4.播放声音
    4.1 采样(Sample)方式
    先定义FSOUND_SAMPLE类型变量,然后就可以使用FSOUND系列函数来实现,如:
    装如文件:
    handle=FSOUND_Sample_Load (0,"YourFileName",0,0,0);  //除文件名外的参数用于多采样或其它等
    FSOUND_PlaySound (0,handle);
    设置音量:FSOUND_SetVolume (handle, 255);
    暂听:FSOUND_SetPaused (handle, true);
    重新开始:FSOUND_SetPaused (handle, false);
    停止:FSOUND_StopSound (handle);
    释放:FSOUND_Sample_Free (handle);
    下面是一个简单的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FSOUND_SAMPLE* handle;
    int main ()
    {
       // 初始化
       FSOUND_Init (44100, 32, 0);
       // 装载和播放
       handle=FSOUND_Sample_Load (0,"sample.mp3",0, 0, 0);
       FSOUND_PlaySound (0,handle);
       // 按任一键结束
       while (!_kbhit())
       {
       }
       // 释放
       FSOUND_Sample_Free (handle);
       FSOUND_Close();
    }
    4.2 流(stream)方式
    先定义一个FSOUND_STREAM 类型变量,然后:
    装入文件:
    handle=FSOUND_Stream_Open("YourFileName",0, 0, 0);
    FSOUND_Stream_Play (0,handle);
       提示:3.7版本之前的方式是不一样的.
    停止:FSOUND_Stream_Stop (handle);
    释放:FSOUND_Stream_Close(handle);
    其它和前面是一样的.下面是一个简单的例子:
    #include <conio.h>
    #include "inc/fmod.h"
    FSOUND_STREAM* handle;
    void main ()
    {
       //init FMOD sound system
       FSOUND_Init (44100, 32, 0);
       //load and play sample
       handle=FSOUND_Stream_Open("sample.mp3",0, 0, 0);
       FSOUND_Stream_Play (0,handle);
       //wait until the users hits a key to end the app
       while (!_kbhit())
       {
       }
       //clean up
       FSOUND_Stream_Close(handle);
       FSOUND_Close();
    }
    5.关闭
    FSOUND_Close ();
    参考:
    A Quick Guide to FMOD by Joachim Rohdehttp://www.gamedev.net/reference/articles/article2098.asp
    FMOD wiki(http://www.devmaster.net/wiki/FMod
  • 看到Fox发表关于成员函数的消息映射的文章,也忍不住发表的一点自己的观点,希望对大家有所帮助。

    其实也就是COMMAND模式的简单实现,看代码吧。

    1. XGUIEventHandlerPointer.h
    namespace XGUI

    {

        class EventHandlerSlot

        {

        public:

            virtual ~EventHandlerSlot() {};

            virtual void execute(const EventArgs& args) = 0;

            virtual void* getClass() = 0;

        };


        template<typename T>

        class EventHandlerPointer :

            public EventHandlerSlot

        {

        public:

            typedef void (T::*EventHandler)(const EventArgs&);

        public:

            EventHandlerPointer() :

                d_undefined(true),

                d_object(NULL)

            {}

            EventHandlerPointer(EventHandler func, T* obj) :

                d_function(func),

                d_object(obj),

                d_undefined(false)

            {}

            virtual ~EventHandlerPointer() {}


            void execute(const EventArgs& args)

            {

                if(!d_undefined) 

                    (d_object->*d_function)(args);

            }


            void* getClass()

            {

                return static_cast<void*>(d_object);

            }


        protected:

            EventHandler    d_function;

            T*                d_object;

            bool            d_undefined;

        };

    }


    1. XGUIWidget.h

     

    namespace XGUI

    {

        // 

        class Widget

        {

        public:

            template<typename T> void addWidgetEventHandler(WidgetEvent EVENT, void (T::function)(const EventArgs&), T obj)

            {

                mWidgetEventHandlers[EVENT].push_back(new EventHandlerPointer<T>(function,obj));

            }

            void addWidgetEventHandler(WidgetEvent EVENT, EventHandlerSlot* function)

                    {

                mWidgetEventHandlers[EVENT].push_back(new EventHandlerPointer<T>(function,obj));

            }

                    bool Widget::fireWidgetEvent(WidgetEvent EVENT, EventArgs& args)

                    {

                          // If there are no User defined event handlers we are done.

                          if(mWidgetEventHandlers[EVENT].empty())

                           return false;

                        

                          // Execute registered handlers

                          std::vector<EventHandlerSlot> userEventHandlers = &(mWidgetEventHandlers[EVENT]);

                          for(std::vector<EventHandlerSlot*>::iterator it = userEventHandlers->begin(); it != userEventHandlers->end(); ++it )

                           (*it)->execute(args);

                        

                          return true;

                 }

            // .

        };

    }


    以上只为部分代码。

    作者:炎龙工作室 千里马肝

    版本:v1.0

    最后更新日期:2002-3-30

    绪言

    在游戏中,因为我们是中国人麻,通常都需要显示汉字,比方说交待剧情。而对于文字的显示,英文的显示要较其简单得多,因为只有26个字母,就算再加一些标点、符号什么的,用一张位图,就可以足以显示所有的单词了,而相关实现技巧,也比较轻松。clip_image002

    而中文的显示方法,要复杂得许多。记得原来在DOS下,汉字的显示都是读的UCDOS的点阵字库,而点阵字库的读取方法,在UCDOS SDK中都有源代码可以参考。但是自从Windows操作系统开始,我们开始了解到一种更好的字库,它就是TTF

    注:以下我所指的开发环境,除非明确说明,默认的平台是VC6.0+DirectX8.1,使用D3D来加速2D。然后使用的STL是用的SGI实现的那一套STL

    点阵字库

    包括现在,有很多游戏都还是使用的点阵字库。因为操作起来比较方便,加上这方面的经验已经积累了好几年了。通常如果只是一种字体就可以满足需要的话,它会是一个比较好、快的解决办法。但是它有3个缺点:

    1.   如果放大显示,不做处理的话,显示出来的汉字,是很难看的。

    2.   像是UCDOS所提供的点阵字库,只有24点阵的有几种字体,如:宋体、黑体、揩体…,而16点阵的好象就只有宋体一种。

    3.   点阵字库,通常是有版权的,尤其是第三方制作的汉字库(如:方正)。

    在这样的情况下,当我们写好这样的一个显示函数,就算是解决了如:放大、快速显示等问题的话,可供选择的字体还是太过于局限了。所以,在字体的要求比较强的情况下,点阵字库并不是一个好的解决方法,他不够灵活。尽管我们对于它的操作是如此得熟练,可以写出优美的代码来展示我们的编程技巧。

    TTF

    TTFTrue Type Font的简称。在Windows\Fonts目录下面,我们可以看到许多后缀为ttf的文件,它就是接下来我们接下来所要谈到的。

    TTF是一种矢量字库。我们经常可以听到矢量这个词,像是FLASH中的矢量图形,在100*100分辨率下制作的flash,就算它放大为全屏,显示出的画面也不会出现马赛克。所谓矢量,其实说白了就是用点和线来描述图形,这样,在图形需要放大的时候,只要把所有这个图形的点和线放大相应的倍数就可以了。而且,在网站上有很多的TTF字库可以下载,或者你可以去买一些专门的字库光盘。然后在你发行你精心制作的游戏时,可以顺便捎上这些后缀为.ttf的文件就行了。包括Quake这样的惊世之作,也都是用的TTF字库。

    这样,我们就可以解决点阵汉字的一些问题。通过TTF,我们在字体的质量和字库的数量上获得了暂时性的胜利。

    字库的读取和显示

    先前谈到点阵字库,只需要很简单的一些操作,就可以显示出想要的汉字。下面我给出一个读取hzk16的函数,它需要一个Surface以供显示用:

    #include <io.h>

    #include <stdio.h>

    #include <conio.h>

     

    // 读取16x16

    void DispHZ16(int x, int y, BYTE *Str, LPDIRECTDRAWSURFACE surf)

    {

             const int Mask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };

             FILE *HzkFp;

             WORD i, j, k=0, m;

             WORD HzNum;

             WORD QuHao;

             WORD WeiHao;

             long offset;

             BYTE dotBuffer[32];      

     

             HzkFp = fopen("HZK16", "rb");

            

             HzNum = strlen((const char *)Str)/2;

     

             DDSURFACEDESC       ddsd;

             LPWORD              lpSurface;

             HRESULT             ddrval;

       

             ddsd.dwSize = sizeof(ddsd);

            

             while((ddrval=surf->Lock(NULL, &ddsd, 0, NULL))==DDERR_WASSTILLDRAWING);

             if(ddrval == DD_OK)

                      lpSurface = (LPWORD)ddsd.lpSurface;

     

             for(i = 0; i<HzNum; i++)

             {       

                      QuHao = Str[i*2]-160;

                      WeiHao = Str[i*2+1]-160;

     

                      offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;

     

                      fseek(HzkFp, offset, SEEK_SET);

                      fread(dotBuffer, 32, 1, HzkFp);

     

                      for(j=0;j<16;j++)

                                for(k=0;k<2;k++)

                                         for(m=0;m<8;m++)

                                                  if(dotBuffer[j*2+k] & Mask[m])

                                                  {

                                                            lpSurface[ddsd.lPitch*(y+j+1) + x+k*8+m] = 0x000000;

                                                  }

                      x+=16;

             }

     

             surf->Unlock(NULL);

     

             fclose(HzkFp);

    }

    其实原理很简单:

    1.         打开字库

    2.         计算字符串长度(这个函数只支持中文),并且Lock Surface

    3.         依次计算出每个汉字所对应的区码和位码(汉字的第1个字节是区码,第2个字节就是位码),然后通过公式计算出这个汉字在字库中的偏移量:
    offset = ((QuHao - 1) * 94 + (WeiHao-1))*32;

    4.         读出一个32个字节的点阵

    5.         绘制到Surface

    以上只是1616点阵字库的显示方法,2424的读取方法与之类似,大家可以参照相关资料来书写出自己的代码。

     

    如何显示TTF字库呢,有很多种手段,下面我按从简单到复杂的的顺序依次介绍:

    1.   使用Windows API,也就是大家所熟悉的TextOut。通过它,还需要一个HDC(设备句柄),我们就可以随意地在屏幕任何地方显示出文字了。

    2.   http://www.freetype.org,有一个FreeType的免费库,而且是OpenSource的。它目前有2个版本:1.02.0。其区别在于,1.0只能读取TTF格式的,而2.0支持更多的文件格式,在使用它之前请详细阅读所要遵循的Licence,以下是摘自FreeType2.0对字库的支持列表:

      • TrueType fonts (and collections)
      • Type 1 fonts
      • CID-keyed Type 1 fonts
      • CFF fonts
      • OpenType fonts (both TrueType and CFF variants)
      • SFNT-based bitmap fonts
      • X11 PCF fonts
      • Windows FNT fonts

    3.   自己研究TTF的格式,然后自己来操作。

    .......
    ╮╮
          \/倒!
         
    虽然我们想要把每一件事情都做好,但是也不是每一件事情都要亲历亲为。如果你非要这样,也行^____^,但是过不了多久,你就会陷入泥沼,到时候你会发现自己的热情正在慢慢被磨灭,什么叫做抓狂,相信你很快就会知道^_^

     

    在有多种选择可以取舍的情况下,我们需要考虑一下,对比一下各种解决方法的优劣。

     

    DirectDraw时代,我们都不自觉地喜欢上了GetDC,因为……多方便啊。可是现在已经到了DirectX8.1时代了(我要使劲地摇那些还沉醉于DirectX7中,为如何在使用alpha时提升那可怜的12fps的朋友们:醒醒,该起床了!),HDC已经被M$列为禁用品。怎么办呢?是的,你可能已经想到了,我们还一直保存着窗口的hWnd呢,可以通过它来得到hdc,从而调用那些需要hdcAPI,可是,这样做是更为愚蠢的,这样对你是没有一点好处的,不信,你就试试吧。有一句话,请牢记:要想你的游戏有更快的速度的话,请不要再去碰HDC了。

    我们非常清楚hdc是一个超慢的解决办法,它无法在我们的高速游戏中满60分及格。下面来看看FreeType,它更像是一个Service。它的解决方法是,先通过一系列的初始化和设置,告诉FreeType字体的名字和大小等,然后它会动态地申请一个Graphic,再把我们要显示的字画到这个Graphic上,你还可以把它保存为tga格式。不过我们最终所想要的不是这个,所以可能我们还需要从这个Graphic上逐点读取或者用CopyRect,然后再画到我们的画面上。其实它已经是很方便的了,可是需要你去学习如何配置和使用它,这是很花时间的一件事情,而且它最大的优点是可以跨平台,我们需要它吗?如果有一个更为简单的办法,像是如果Textout不是那么慢的话,就好了……

    在这里,顺便谈一下另2个字体显示类:ID3DXFontCD3DFONT。可能早就有人会说怎么在上面的列表中没有它们?原因我会在下面慢慢地说明:

    ID3DXFont,它存在于D3DX库中,一个现成的字体类,不过对于它的处理方法……我实在不敢恭维,就引用一位大师所说的话来表达我的看法吧: 在内部实现中, ID3DXFont::DrawText()函数确实做了我上面讨论的工作,先建立一张GDI兼容的位图,把文本绘制到位图上,而后把位图拷贝到纹理贴图上去,最后把纹理渲染到屏幕上。这样你就聚齐了所有的龟速的原始GDI函数,还包括了一大堆的额外开销 — 最终,这个函数比原来GDIDrawTextEx()函数要慢上超过六倍……

    CD3DFONT,是由M$D3D的框架代码中提供。不过它只能显示英文,有很多朋友通过自己定制和修改这个类,来实现自己的中文显示。不过效果都不是很好。其实原理,跟ID3DXFont的方法差不多,不过处理方法要聪明了一点。

    分析与思考

    那么我们应该怎么办呢?通常我们会幻想,如果可以像处理英文那样,把所有的汉字都保存在一张位图里,该有多好。这样,显示的速度就不是问题了,直接可以CopyRect上去。可是,这样可能吗?首先,必须每一种字体都要生成这样的一个巨型位图。而且据说在GB2312中,一共有6000多个汉字,就算是用16*16oh my god,这个位图该有多大啊(据说会有2.5M^__^)!!!而且在DirectX8.1中,对于Texture(显示的最小单位,就好象是原来DirectSurface的概念一样。说过多少遍了,不要再用DirectX8以前的东西了。不要试着去回忆那些美好的过去,我很明白,要你一下子放弃原来多年所获得的成就,是一件很痛苦的事情,但是包袱太重,是会影响进步的。就像是我们的国家……扯远了),不同的显卡,支持的最大容量也是不同的。比方说早期的Voodoo,只支持256*256大小的Texture。而在我的显卡(Geforce2 MX 200)上测试,支持最大2048*2048大小的Texture。对于这样的硬件不确定性,我们只能取其最小值,也就是256*256

    汉字虽然很多,但是常用的汉字,其实也就只有那么几百个。像这样的字:鬯、鞴,你一辈子会看到多少次呢?如果可以做一个类似于Cache的东西,保存着常用的那些个汉字,在需要显示的的时候,先在Cache中查找,如果有的话,就马上画上去;如果没有,就从字库中提取到Cache中。这样的话,在使用Texture来保存汉字的位图信息的同时,对于每个汉字,我们还要定义一个结构,然后用一个东西把它串起来,综合它们2个,也就实现了我们所要的Cache了。刚开始,我所定义的结构是这样的:

    struct Char{

      char hz[3];   // 保存汉字

      int frequency;// 使用频率

      RECT rect;    // 这个字对应位图的区域

      Bool isUsing; // 是否使用

    }

    对于汉字和英文,我在这里大概地讲一下原理:汉字是由2个字节保存,而英文只需要1个。而判断一个字是否是汉字,只需判断第1byte是否>128(在原来的GB2312中,汉字的2个字节都是>128的。而新的GBK字库,汉字的第2个字节不一定>128,我想这是扩大了字库容量的原因。我的意思是说,如果给一个字符串你,随机给其中一个位置,然后我问你这个位置是什么?你的回答只能是:1 英文 2 汉字的首字节 3 汉字的尾字节。而这个问题的解法,为了稳妥起见,你必须从字符串的开始判断起)。也就是说在char[3]中,如果保存的是汉字,则char[0]保存汉字第1个字节,char[1]保存汉字第2个字节,第3个存放’\0’;如果是英文的话,则只用到char[0],其它的全部为’\0’

    接下来,对于使用char[3]来保存汉字,是否真的很合适呢?因为如果把它当作一个字符串来看的话,在查找时就需要使用 strcmp 来比较字符串了,这样一定是会影响速度的。如果不把它看作字符串(字符串的最后一个字节需要以’\0’结尾),只用char[2]的话,我们可以只是简单地调用宏MAKEWORD,把2byte压成1WORD。当把文字作为一个WORD来看的时候,这样查找比较时可以用WORD内建的==操作,这样要比调用strcmp函数要快得多。

    int frequency用来标志每个WORD的使用频率。设想,如果一个字已经存在于Cache中,以后每对它调用一次,就让frequency++。这样做还有一个用意是,是否可以在一个合适的时候,以frequency为参照来对这整个Cache排个序,把常用的字放在前面。那么在显示时,可以先在Cache中查找所要显示的字是否已经存在于Cache中,如果有则直接显示,没有的话才需要采取某种手段将字加入到Cache中。一些常用的字(像:我、的、着、了、过……),使得显示的速度将会大大提高。

    其实上面说了半天的Cache,它具体是什么呢?其实就是指的最小绘制单位,在DirectX7里是Surface,而在DirectX8中就是Texture。使用它来存放显示过的汉字,这样,就不用每次都从字库中读取或是调用如TextOut这类GDI超慢的函数了。因为每次在绘制一个文字之前,都会先在这个Cache中找,有的话就直接画上去,没有才会调用TextOut操作。而这样做的原因,我们先设想一下:游戏一般会控制为30fps或是60fps的速度不停地刷新,如果在GameLoop中有任何的代码是龟速级的话,这样就会导致fps的最大数的降低,也就意味着在保证30fps60fps的同时,能绘制到屏幕上的物体的数量减少了。这就是我们为什么要使用Texture来作为Cache的实现的原因。再一个,文字在屏幕上显示时一般会保持一段时间,这个时间可能是1-3秒,我们的游戏也就会相应地更新60fps180fps,这是因为人们需要阅读它们。或者是一些如标题这样的文字,它们总是不会更新的,或是更新得很慢。我们完全可以在第一时间,比方说我们的画面有60fps,在第1fps时,我们得知要显示文字,然后先在Cache中找,结果很糟:没有找到!这时马上用TextOut写到Texture上(现在还是属于第1fps的时间范围内),而接下来的59fps(甚至更多),都不用再调TextOut了,而是直接从我们的CacheTextureCopy到屏幕上,速度得到了保障。谈到GDI的函数,为了实现设备无关性,它们的速度都很慢。其实它们也不像说得那么慢,如果不是每一帧都要调用它们,也算是蛮快的^_^。那么这个RECT rect,就代表着这个文字所对应在Texture上的区域位置。

    使用什么东西来把这nChar串起来呢,一般会想到的是链表,原因无非有2个:1 随时有新的字加进来,而内存是不连续的 2 它几乎没有容量的限制(除非是内存用完了)。不过链表的访问速度是很慢的,如果使用像数组这样的东西就好了。仔细想想,在这里,我们用来存储的Cache,最大也就是256*256(理由上面说了),所以大小应该会是固定的。我们只需要在数组中的给每一个汉字加上一个标志,说明这个位置的使用情况。那么就使用数组吧,这样的话,访问的速度要更快一些,直接首地址+偏移量就够了,不必像链表,在查找时需要逐node访问。当然,我绝不会想到用new  Char来申请这个数组。因为这样做实在没有必要,请不要过于迷信自己的能力,在STL中已经有vector了,为什么还要自己写呢?^_^最后的一个bool成员变量isUsing,也就是上面所说,用来标志使用情况的。

    实际的操作

    上面考虑了那么多,我认为都是实际操作之前所应该有的。先谈谈如何显示吧,因为在DirectX8.1中已经将DirectDrawDirect3D融合为DirectGraphics了。所以无法像原来那样了…………哦,实在有太多东西要讲了,我还是推荐几篇文章给你吧^_^

    http://vip.6to23.com/mays/develop/directx/200201/Geczy3Din2D.htm

    http://vip.6to23.com/mays/develop/directx/200201/GESurface.htm

    http://vip.6to23.com/mays/develop/directx/200112/2DGtoDX8.htm

    http://vip.6to23.com/mays/develop/directx/200201/DX8adv2D.htm

    接下来,我会假设你已经具备了在DirectX8.1中绘图的基本概念了,所以在你继续往下阅读之前,请务必先仔细阅读以上推荐的文章。

    前面提到,需要一个vector来对应Texture上各个位置文字的信息,上面已经创建了一个结构Char,则这个vector的定义为:

        vector <Char> _vBuf;               // 记录缓冲中现有的文字情况

    首先,由于可以利用硬件的放大缩小机能,所以字体的大小精度要求不是很高,只需要支持16*1624*24大小的字体就可以了。我们需要一个这样的初始化函数:

    bool CFont::

    /*-------------------------------------------------------------

    LPDIRECT3DDEVICE8 pd3dDevice  ---  D3DDevice设备

    char szFontName[]          ---  字体名(: 宋体)

    int nSize                  ---  字体大小, 只支持1624

    int nLevel                 ---  纹理的大小级别

    -------------------------------------------------------------*/

    Init( LPDIRECT3DDEVICE8 pd3dDevice, char szFontName[], int nSize, int nLevel )

     

    DirectX8.1中,由SetTexture(…)所贴的图的大小,也就是Texture的大小,是有大小限制的,长和宽都必须是2^n,而且位图越大,所花费的显存越大,这样留给其他显示用的显存就少了。所以,必须根据需求的不同,来自定Texture(也就是Cache)的大小。因为汉字点阵大小的原因,所以从实用角度而言(比方说只是显示fps或是短小的标题),开辟一个64*64大小的Texture,才能满足最低情况下的需要(这时如果选择16点阵的话可以存放16个汉字,24点阵可以存放7个,依次类推……)。

    根据设置,创建Texture

        _TextureSize = 32 << nLevel;       // 纹理大小

        _TextSize  = nSize;            // 文字大小

        _TextureSize = 32 << nLevel;       // 纹理大小

       

        _RowNum = _TextureSize / _TextSize;    // 计算一行可以容纳多少个文字

        _Max = _RowNum * _RowNum;          // 计算缓冲最大值

     

    创建字体,还是需要使用Win32 API。也就是先创建一个HDC

        _hDc = CreateCompatibleDC(NULL);

     

    然后创建一个BITMAP和一个FONT,将它们与HDC关联起来。

        LOGFONT LogFont;

        ZeroMemory( &LogFont, sizeof(LogFont) );

        LogFont.lfHeight         = -_TextSize;

        LogFont.lfWidth             = 0;

        LogFont.lfEscapement     = 0;

        LogFont.lfOrientation       = 0;

        LogFont.lfWeight         = FW_BOLD;

        LogFont.lfItalic         = FALSE;

        LogFont.lfUnderline         = FALSE;

        LogFont.lfStrikeOut         = FALSE;

        LogFont.lfCharSet        = DEFAULT_CHARSET;

        LogFont.lfOutPrecision      = OUT_DEFAULT_PRECIS;

        LogFont.lfClipPrecision     = CLIP_DEFAULT_PRECIS;

        LogFont.lfQuality        = DEFAULT_QUALITY;

        LogFont.lfPitchAndFamily = DEFAULT_PITCH;

        lstrcpy( LogFont.lfFaceName, szFontName );

       

        _hFont = CreateFontIndirect( &LogFont );

        if ( NULL == _hFont )

        {

           DeleteDC( _hDc );

           return false;

        }

       

    (只需要创建一个字体大小的BITMAP即可)

        BITMAPINFO bmi;

        ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));

        bmi.bmiHeader.biSize     = sizeof(BITMAPINFOHEADER);

        bmi.bmiHeader.biWidth        = _TextSize;

        bmi.bmiHeader.biHeight      = -_TextSize;

        bmi.bmiHeader.biPlanes      = 1;

        bmi.bmiHeader.biBitCount = 32;

        bmi.bmiHeader.biCompression = BI_RGB;

       

    (这里需要定义一个指针指向位图的数据:

        DWORD *       _pBits;           // 位图的数据指针)

     

        _hBmp = CreateDIBSection( _hDc, &bmi, DIB_RGB_COLORS,

           (void **) &_pBits, NULL, 0 );

        if ( NULL == _hBmp || NULL == _pBits )

        {

           DeleteObject( _hFont );

           DeleteDC( _hDc );

           return false;

        }

       

        // hBmphFont加入到hDc

        SelectObject( _hDc, _hBmp );

        SelectObject( _hDc, _hFont );

     

    接着设置背景色和文字色:

        SetTextColor( _hDc, RGB(255,255,255) );

        SetBkColor( _hDc, 0 );

     

    设置文字为上对齐:

        SetTextAlign( _hDc, TA_TOP );

     

    创建Texture所需要的顶点缓冲:

        if ( FAILED( _pd3dDevice->CreateVertexBuffer( _Max * 6 * sizeof(FONT2DVERTEX),

           D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0,

           D3DPOOL_DEFAULT, &_pVB ) ) )

        {

           DeleteObject( _hFont );

           DeleteObject( _hBmp );

           DeleteDC( _hDc );

           return false;

        }

       

    创建Texture

        if ( FAILED( _pd3dDevice->CreateTexture( _TextureSize, _TextureSize, 1, 0,

           D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, &_pTexture ) ) )

        {

            DeleteObject( _hFont );

           DeleteObject( _hBmp );

           DeleteDC( _hDc );

           SAFE_RELEASE(_pVB);

           return false;

        }

     

    设置渲染设备的渲染属性:

        _pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE,   TRUE );

        _pd3dDevice->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA );

        _pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );

        _pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE,     TRUE );

        _pd3dDevice->SetRenderState( D3DRS_ALPHAREF,         0x08 );

        _pd3dDevice->SetRenderState( D3DRS_ALPHAFUNC,        D3DCMP_GREATEREQUAL );

        _pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );

          

        _pd3dDevice->SetTexture( 0, _pTexture );

        _pd3dDevice->SetVertexShader( D3DFVF_FONT2DVERTEX );

        _pd3dDevice->SetStreamSource( 0, _pVB, sizeof(FONT2DVERTEX) );

     

    设置缓冲的最大容量

        _vBuf.resize( _Max );

     

    这样,初始化完成了。接下来是如何把一个汉字写到Texture中,以及如何进行管理。定义函数:

    // 得到文字在纹理中的位置

    void CFont::

    /*-------------------------------------------------------------

    char c1   ---  文字的第1个字节

    char c2   ---  文字的第2个字节

    int & tX  ---  写入纹理中的坐标x

    int & tY  ---  写入纹理中的坐标y

    -------------------------------------------------------------*/

    Char2Texture( char c1, char c2, int & tX, int & tY )

    {

        WORD w = MAKEWORD(c1, c2);      // 把此字变为WORD

        vector<Char>::iterator it = find( _vBuf.begin(), _vBuf.end(), w );

        if ( it == _vBuf.end() )    // 如果没找到

        {

           it = find( _vBuf.begin(), _vBuf.end(), 0 ); // 查找空闲位置

           if ( it == _vBuf.end() ) // 缓冲已满

           {

               for(; it!=_vBuf.begin(); it-- )

               {

                  it->hz = 0;

               }

    //         Log.Output( "字体缓冲已满, 清空!" );

           }

     

           // 计算当前空闲的Char在缓冲中是第几个

           int at = it-_vBuf.begin();

     

           // 得到空闲位置的坐标

           tX = (at % _RowNum) * _TextSize;

           tY = (at / _RowNum) * _TextSize;

     

           // 设置这个Char为使用中

           (*it).hz = w;

     

           RECT rect = {0, 0, _TextSize, _TextSize};

           char sz[3] = {c1, c2, '\0'};

           // 填充背景为黑色(透明色)

           FillRect( _hDc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH) );

           // hBitmap上写字

           ::TextOut( _hDc, 0, 0, sz, c1 & 0x80 ? 2 : 1 );

          

           // 锁定表面, 把汉字写入纹理, 白色的是字(可见), 黑色为背景(透明)

           D3DLOCKED_RECT d3dlr;

           _pTexture->LockRect(0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);

           BYTE * pDstRow = (BYTE*)( (WORD *)d3dlr.pBits + tY * _TextureSize + tX );

          

           for (DWORD y=0; y<_TextSize; y++)

           {

               WORD * pDst16 = (WORD*)pDstRow;

               for (DWORD x=0; x<_TextSize; x++)

               {

                  BYTE bAlpha = (BYTE)((_pBits[_TextSize * y + x] & 0xff) >> 4);

                  if (bAlpha > 0)

                      *pDst16++ = (bAlpha << 12) | 0x0fff;

                  else

                      *pDst16++ = 0x0000;

               }

               pDstRow += d3dlr.Pitch;

           }

           _pTexture->UnlockRect( NULL );

        }

        else

        {

           // 计算当前空闲的Char在缓冲中是第几个

           int at = it-_vBuf.begin();

     

           // 得到这个字的坐标

           tX = (at % _RowNum) * _TextSize;

           tY = (at / _RowNum) * _TextSize;

        }

    }

    以上代码中的注释已经很清楚了,相信无须我多言。这里唯一需要声明的是:原来所定义的Char结构是这样的

    struct Char{

      char hz[3];   // 保存汉字

      int frequency;// 使用频率

      RECT rect;    // 这个字对应位图的区域

      Bool isUsing; // 是否使用

    }

    后来因为将char hz[3]合成为WORD,所以改为WORD hz。然后对于int frequency,这个词频应该如何表现,我一直没有想到很好的方法。frequency应该在何时++呢?是在每次被使用的时候吗?但是这样的话,上面说过,游戏是以60fps的速度在刷新,如果停上1分钟的话,变量很快就会溢出了。就算是使用像是DWORD__int64这样的巨型变量保存,也是不安全的。除非能在某个合适的时候将frequency清零,但是这个“时候”是什么时候呢?或者设置一个最大值,如65535,但是这样也基本上没什么用途,很快,所有在vector中的Char中的frequency都会++65535的。回忆一下最初,是因为想把常用字放到vector的前面,以便每次find操作可以最快返回结果的。而经过我的测试,即使不做这样的优化操作,速度也是很快的,毕竟Cache不是很大,加上vector是连续内存空间。所以可以放弃使用int frequency

    然后对于RECT rect,因为没有了int frequency,意味着一旦将汉字写入到Texture,其位置就不会变动了。所以,很容易根据find函数操作后的iterator,直接计算出这个汉字所在Texture的位置。这样,RECT rect也不再必须。

    bool isUsing,它本身就是个鸡肋,要也可以,这样结构更加清晰。不过,直接通过观察WORD hz0或非0,即可实现isUsing的作用了。

    为什么要对结构Char这么精雕细琢呢?

    1.  既然没有必要的东西,就应该删除

    2.  Char结构的大小越大,vector所要求的内存越大

    3.  小的结构,find可以更快地查找出所结果

    为什么find会正常工作呢?这里我要大概地讲一下find是如何查找出所需的位置的:它只是简单地使用whilevectorbegin一直遍历到end,逐个判断,直到找到为止。find要求必须实现自己的operator ==(),进一步跟踪到find的源码中,发现也是这样。于是前面的结构Char变成了现在这样:

       struct Char{

           WORD   hz;           // 文字

     

           Char() : hz(0) {}

     

           // 用作查找文字

           inline bool operator == ( WORD h ) const

           {

              return hz==h ? true : false;

           }

       };

    是不是很简单?^___^

     

    终于到了显示的函数了:

    // 得到文字在纹理中的位置

    bool CFont::

    /*-------------------------------------------------------------

    char szText[]  ---  显示的字符串

    int x        ---  屏幕坐标x

    int y        ---  屏幕坐标y

    D3DCOLOR     ---  颜色及alpha

    int nLen     ---  字符串长度

    float fScale   ---  放大比例

    -------------------------------------------------------------*/

    TextOut( char szText[], int x, int y, D3DCOLOR color, int nLen, float fScale )

    {

       Assert( szText!=NULL );

     

       float sx = x, sy = y,

             offset=0, w=0, h=0, tx1=0, ty1=0, tx2=0, ty2=0;

       w = h = (float)_TextSize * fScale;

     

       char ch[3] = {0,0,0};

       FONT2DVERTEX * pVertices = NULL;

       UINT wNumTriangles = 0;

       _pVB->Lock(0, 0, (BYTE**)&pVertices, D3DLOCK_DISCARD);

     

       if ( -1 == nLen ||              // 默认值-1

            nLen > lstrlen( szText ) ) // 如果nLen大于字符串实际长度, nLen=实际长度

           nLen = lstrlen( szText );

       for (int n=0; n<nLen; n++ )

       {

           ch[0] = szText[n];

     

           if ( ch[0]=='\n' )

           {

              sy+=h;

              sx=x;

              continue;

           }

     

           if ( ch[0] & 0x80 )

           {

              n++;

              ch[1] = szText[n];

              offset = w;

           }

           else

           {

              ch[1] = '\0';

              offset = w / 2 ;

           }

     

           int a, b;

           Char2Texture( ch[0], ch[1], a, b );

      

           // 计算纹理左上角 0.0-1.0

           tx1 = (float)(a) / _TextureSize;

           ty1 = (float)(b) / _TextureSize;

           // 计算纹理右上角 0.0-1.0

           tx2 = tx1 + (float)_TextSize / _TextureSize;

           ty2 = ty1 + (float)_TextSize / _TextureSize;

     

           // 填充顶点缓冲区

           *pVertices++ = FONT2DVERTEX(sx,    sy + h, 0.9f, color, tx1, ty2);

           *pVertices++ = FONT2DVERTEX(sx,    sy,    0.9f, color, tx1, ty1);

           *pVertices++ = FONT2DVERTEX(sx + w, sy + h, 0.9f, color, tx2, ty2);

           *pVertices++ = FONT2DVERTEX(sx + w, sy,       0.9f, color, tx2, ty1);

           *pVertices++ = FONT2DVERTEX(sx + w, sy + h,   0.9f, color, tx2, ty2);

           *pVertices++ = FONT2DVERTEX(sx,    sy,    0.9f, color, tx1, ty1);

     

           wNumTriangles+=2;

     

           sx+=offset;   // 坐标x增量

       }

       _pVB->Unlock();

       _pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, wNumTriangles );

     

       return true;

    }

    结束语

    记得有一句名言: Keep it simple and stupid.在实现功能的同时,保持代码简单、清晰是非常重要的一件事。相信在往后的日子里,在不论是别人阅读或是你自己回顾的时候,你都会发现一如既往地遵守这个守则,是多么得重要!

    相信通过上面我那无数的废话,加上代码中还算足够的注释,聪明的你一定能够明白这其中的原理了吧。如果以上的内容还不足以让你完全搞清楚的话,你可以登录我的主页:

    炎龙工作室

    上面不仅包括了上面所写的程序代码,还有一个用来演示效果的一个很简单的demo

    说明,以上所实现的CFont是包含在我的游戏引擎中的一个部件,而目前已经实现的部件包括有:

    1.  CGameFrame(游戏框架类)  -----  封装了窗口及D3D设备的建立,需要派生出自己的子类

    2.  CAudioCSound(声音类) -----  支持wav/mid/mp3的播放

    3.  CDirectInput(控制类)    -----  键盘、鼠标操作

    4.  CDirectShow(视频类)     -----  支持avi/mpg/mov等的播放

    5.  CSpriteX(精灵类)        -----  方便游戏中对精灵的控制

    6.  CFont(字体类)           -----  中英文字体的显示

    7.  CTimer(时间类)          -----  高精度时间的控制

    8.  FPSfps 类)             -----  fps的计算

    9.  LOG(日志类)             -----  游戏中的错误反应以及状态记录

    最重要的是,这个Game Engine完全是开放源代码的。关于更新的情况、版本说明以及源码下载,请随时关注我的主页!

    接下来,我将会继续完善这个Engine,可能加入的有:高效粒子系统、斜45度角地图……

    头文件,
    处理憔悴,
    编译器,
    报错如风雪。
    是谁混淆基本类,
    惹变量是非。
    虚基类,
    构造轮回,
    动态堆,
    字符串唤不回。
    纵然代码已经成灰,
    内存不灭。
    循环如三千东流水,
    我嵌套一瓢爱了解,
    只恋函数的递归。
    你发如雪,
    定义了离别,
    我指针指向了谁,
    邀明月,
    让地址皎洁,
    爱在数组里倾颓。
    你发如雪,
    初始化眼泪,
    我联编继承了谁,
    数据醉,
    编程的岁月,
    我用后悔,
    刻电脑崩溃的碑。
    啦儿啦 啦儿啦 啦儿啦儿啦
    啦儿啦 啦儿啦 啦儿啦儿啦
    去约会她斜扎儿马尾只因为学C++学到了崩溃

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    int RussianMul(int n, int m)
    {
    int remain = 0;
    while(n != 1)
    {
    // odd
    if (n % 2 != 0)
    {
    n = (n-1)/2;
    remain += m;
    m *= 2;
    }
    // even
    else
    {
    n /= 2;
    m *= 2;
    }
    }
    return m + remain;
    }

    上周,“番茄花园”作者洪磊被警方带走,传出的罪名是涉嫌侵犯微软著作权,也就是俗称的“盗版”。(8月24日北京晨报)


      从番茄花园的站长被拘捕、番茄花园网站改版开始,“番茄门”开始成为网络间仅次于奥运会的热门话题。与当初人们对“珊瑚虫事件”的反应有些类似,有一部分网民开始为番茄花园叫屈,“支持番茄花园”式的呼吁此起彼伏。


      对于部分网民的这种反应,我理解他们的苦衷,但从更长远的方向看,笔者又反对这种支持番茄花园的举动。我想对他们说:为了我们自己的利益,请不要支持番茄花园。


      支持番茄花园就意味着支持盗版、支持盗版就将我们置于被动的地位,这是一个很浅显很直白的道理。网民们并非不懂这些,只是因为弱者的激愤才有了前面那些不正常的支持。盗版产生并且泛滥的主要原因是消费者的经济条件与正版软件的销售价格难以匹配,如果我们普通人的财力足以接受售价上千元的微软系统并且不拿它当回事、如果微软的系统免费或者价格低的与盗版别无二致,那盗版不大可能会有源头也不大可能会有市场。但在垄断和经济条件面前我们普通消费者是弱者,所以支持廉价甚至免费的盗版这个错误的选择成了部分网民在番茄花园站长被抓后第一反应。


      实际上,网民们支持番茄支持盗版的想法有些天真,他们也许认为只要我们用盗版支持盗版的话微软就无可奈何、微软就有可能因此蒙受巨大的损失。但是,部分网民的支持盗版能改变微软独霸天下的垄断地位吗?只能是加强这种地位;部分网民的支持盗版能够使微软的“天价”降低吗?这近乎于天方夜谭。支持盗版只能让微软有足够的理由在知识产权方面对中国指手画脚、支持盗版只能让微软的垄断地位毫发无损、支持盗版只能让微软的天价居高不下、支持盗版只能让国产软件业继续没落,支持盗版,对我们有什么好处?一点好处也没有。


      我们普通百姓希望自己使用的软件有低廉的价格(最好是免费)、简便的操作、能够得到保障的质量,但很难实现,所以才会退而求次选择盗版。可如果正版具有远胜于盗版的优点的话你还会用盗版吗?很明显,我们当然会去用正版。那么,如果我们的政府能凭借法律的力量强迫微软实现上述目标的话,我们的目的不就达到了吗?所以,我们不应该寄希望于用盗版去打击微软,这违反了法律;我们应该推动政府用法律去制裁微软,因为已经有了微软在欧洲和美国被起诉的先例。


      如果我们某些人还在为番茄花园的倒掉惋惜的话,那说明我们很短视;如果我们还在为支持番茄花园摇旗呐喊的话,那我们是在拖《反垄断法》的后腿。这对我们毫无益处。我原来以为“番茄门”是微软拖延时间的一个手段,后来我反过来一想,这何尝不是我们《反垄断法》解决微软垄断问题的一个良好契机呢?


      廉价或者免费不是我们的终极目标,从长远看我们应该有更远大的理想。我想劝那些支持番茄的人们几句:你不愿意花钱,你可以去用Linux,如果使用不习惯,你可以学习或者等待,但为了我们自己的利益,请不要去支持番茄花园。

    很多人都说,现在社会风气败坏了,都是年轻人的错。这实在是一个很离谱的理论,至少从我的观察来看,比如拿坐公交车这件事情来说,最不道德就是老年人(不是所有的老年人),从公交车到站,他们就挤成一团,谁都恨不得自己第一上车;只见过年轻人给老年人让座,但是基本没有见过老年人给孕妇或者抱小孩的让座;给老年人让座位,能得到“谢谢”的次数也不多;在无锡,我至少看到三次老年人之间为了一个座位吵架甚至要出手。就拿近期发生的一件事情来看,南京的“彭宇案”来看,是那个老年人还是彭宇不够道德呢?
      其实我不是在抨击老年人,也不是在为年轻人做辩解,社会不好大家都有责任。但是就从社会风气不好的源头来看,至少这笔帐不应该,也不能够算到年轻人身上。一个社会,其权力基本是掌控在中老年人手里,最简单,看看我们的政治局常委就应该清楚,里面没有80后吧。
    目前中国的社会问题在于老百姓生活艰难,没有保障,社会没有正义,没有诚信,缺少关爱。这一切都是什么造成的呢?老百姓的保障,从国企改革,员工下岗,医疗改革,教育改革,住房改革,一层层的被剥落下来,这过程中,做决策,执行的都是哪个年龄段的人?从这个角度来说,作为一个80生人,被扔到这样一个烂摊子的社会里面,是有权力责怪我们的长辈的。由于失去了保障,而在保障失去的过程中,其实都是国家的违约。国家的步步违约造成老百姓对国家不信任,原先的道德体系崩盘。人失去的多了,就开始拼命的想获得,而大部分的社会资源掌控在少部分的人手里,剩下的人就相互践踏,你抢我夺。就连重庆家乐福搞促销那么点蝇头小利都会导致3人遭挤压致死,在现实的社会中可想而知,人会用尽各种手段,尔虞我诈,无所不用其极的想办法生存。有能力的就贪污受贿,有专职的就收受红包,有权力的就搞灰色交易,剩下的老百姓就想办法出去偷,出去骗,出去抢。还有一些许三多同志,他们就老老实实的当民工,当没有什么保障的工人。
      社会道德的沦丧,不能责怪某些人,同样是人,为什么在唐代的品质和清朝的就不一样的,就是清朝的人,为什么清初和清末的社会风气也不一样呢?非人异也,社会制度使然也。
    但是,就是这样,我相信现在的年轻人还是有资格指责一下中老年人的,他们更多的时候只是这个社会现状的承受者,而那些年富力强和已经行将就木的人,才是当前社会的缔造者和执行者。举个最简单的道理,经历过大跃进年代的人,就不要和我讨论什么诚信了,经历了文革的人,也不要和我讨论什么正直。诚信的人已经被打压了,而正直的人,就如同老舍。除了那些被打压的,受压迫的,剩下的顶多在当时是一个苟活者。
      我倒是认为中国的希望在80后,他们比他们的前辈看到的更多,听到的更多,学到的更多。随着大学教育的普及,虽然我并不苟同杨振宁教授关于中国的大学教育是最成功的观点,但是大学的教育还是为中国培养了大量的有一定知识的年轻人。更重要的是,在他们这一代人里面,他们很少有人再靠依赖国家的组织、部门来混饭吃了,他们可以通过自己的努力找到一份养活自己的工作,哪怕是洗脚工。从这一点来说,他们的行为更自由了,自由身体可以带来自由的思想,自由的思想可以带来独立的意识。中国之希望,或许就在80 以后。

    村民石章云3年前患了食道癌,现病重不起

    刘长月老人在发臭的黑水河上,吃力的挪动着渡船

    王子清已记不清自己主持过多少次葬礼了。

    自打他48岁那年起,癌症患者便在村子里多了起来,死亡接踵而至。每遇死亡,王子清必会到场,或主持,或与亡魂告别。多数情况下,他是主持者。

    本来,作为子字辈的他,是没有资格主持这种仪式的。子字辈之上,还有祖父辈的玉字辈和父辈的德字辈。在村里,这种告别亡魂的庄严仪式,通常是由年岁较大且德高望重的老人主持。但如今村子里玉子辈的人已所存无几,而德字辈的人,或因癌症相继去世,或已“逃离”了这个村子。因此他这个小子辈,就责无旁贷地担当起了这个任务。岁岁月月,“送走一茬又一茬”。

    “每死一个人,我的心就像刀割一样。”现年65岁的王子清步履蹒跚地走在村中,神情黯淡。有时,他还会停住脚步,摆摆手,不去追忆往事,“都是大好的年龄,死的太可惜了。真不知道啥时才是个头啊!”


    死人就像家常便饭一样

    位于河南省沈丘县城东约10公里处的东孙楼村,共有1200多人。原先孙姓为望族,后王姓成了大姓。现全村王姓约有800多人。

    自上世纪90年代以来,村里的人出现了密集的反常现象:有些人腹泻不止,有些人内脏出现了各种不适。此外,偏瘫、智障、畸形和妇科疾病也频频出现。患者相继死去,少则一年内五六人,多则达20多人。死者经诊断多为食道癌、肝癌、胃癌、直肠癌、子宫癌、乳腺癌等等。

    死亡像个挥之不去的幽灵,笼罩着这个村子。新起的坟茔、白对联、哀乐,一年四季绵延不绝。

    王子清的族人便是这个不幸村庄的不幸家族。

    1991年,小他5岁的弟弟食道出现问题,吞咽困难。次年,大他两岁的哥哥也出现同样病症。俩人的病后来都转化为食道癌。2004年6月,王子清的哥哥病逝。28天后,他的弟弟也撒手人寰。这边丧事还没料理完,3天后,他的一位叔叔也死于食道癌。短短一个月,王子清相继失去3位亲人。

    而这仅仅是幽灵掀开的序幕一角。事实是,这些年,仅王姓德字辈的族人中,25对夫妇,便有19人死于癌症。而子字辈中不到80人,便有16人死于癌症。死者大都正值壮年,最大的70岁出头,最小的只有30岁。

    “死人就像家常便饭一样。”王子清抽泣着,任由眼泪淌满褶皱的脸上,“我不知道村里到底死了多少人,只知道一个接一个地死。”

    王子清本人虽然没有患上癌症,但胃穿孔也让他遭了不少罪。2004年,他花了5000元做了胃部手术,至今天气转凉,伤口就会隐隐作痛。为“转移痛苦”,他染上了抽烟的毛病,一支接一支,一会儿功夫,一包烟就空了。

    在大多数地区,通常每个村里有一名医师就不错了,但在沈丘县东孙楼村却增加到了4名医师。刘德亮是村里最老资格的医师。他记得最忙碌时自己每天要跑三四家,“抗生素药供不应求”。村里的人口一度竟出现了负增长。

    一位正在病床上输液的腹泻患者挣扎着坐了起来,插话说,就是现在,村里患腹泻的病人至少也不下100人。

    不仅是东孙楼村,自上世纪90年代以来,沈丘县的黄孟营村、孟寨村、孙营村等,癌症患者的比例均大幅度上升。据该县政协常委、民间环保组织“淮河卫士”会长霍岱珊提供的资料显示:1990年~2005年间,2470人的黄孟营村,有116人死于癌症;2366人的孟寨村,有103人死于癌症;1697人的孙营村,有37人死于癌症;1300人的陈口村,有116人死于癌症;2015人的大衤者庄,有145人死于癌症;1687人的杜营村,有187人死于癌症。而据沈丘县医院记载,1972年当地120万人中,只发现癌症患者12人,发病率仅为十万分之一。

    由于癌症爆发的密度大、频率高,故上述村庄被当地人称作“癌症村”。3年前,河南省周口市有关部门,曾联手在沈丘县的“癌症村”做过一次癌症患者的入户统计,但该数据一直没有公开,沈丘县卫生局的一位副局长表示,不能向记者提供。

    谁得病谁家就败

    在王子清隔壁的一条小巷子里,原先住着16户人家,现在只剩下3户了。其中,有两户全家死于癌症,其余的因害怕也搬走了。

    恰逢雨后,巷子里一片泥泞。在这条约100米深的小巷子里,已有很长时间没人出入了,显得分外寂静。有一户人家的房子是两层预制板楼,在这个人均年收入不到1000元的村子里,这栋小楼显得十分气派。但如今铁门上的锁头已经生锈,门口杂草丛生。显然,这里早已人去楼空了。

    “死的死,走的走。”王子清趿着拖鞋,在泥水中“啪嗒啪嗒”地走着,不时发出重重的叹息,“以前这里可热闹了,现在一点生气都没啦。”

    这个东西长约1公里、南北宽约半公里的村子,上世纪90年代以前,曾和许多村一样,过着自给自足的闲适生活。农忙时,男人女人热火朝天地干活,换取一年的口粮。收成好的时候,兴许还会落些余钱,添几件衣服,买两件电器,乐呵一番。不忙的时候,男人们出外找些活计,或在本地打打零工。空闲时,招呼几个人喝上几盅,或搓一阵麻将。

    可眼下,疾病和死亡像恶魔一样几乎缠扰着村里的每一户人家。街上偶尔遇到几个人,脸上也大都带着悲伤、无奈或茫然。有时能看到三五个人聚在一起,谈论的话题往往都和癌症有关。

    一位癌症患者盖着一床厚被子躺在床上呻吟。经过化疗和放疗,她的头发已经稀稀疏疏,头皮清晰可见。裸露在外的胳膊和腿,瘦得皮包骨头。她紧闭双眼,嘴里不时嘟囔着什么。守在她身边的丈夫,愁眉不展,目光呆滞,偶尔下意识地给妻子掖掖被子。“家里有多少钱,也让病折腾穷了。”他说。

    在这三间预制板结构的楼房里,大理石地板、雪白的墙壁、宽大明亮的窗户、墙上四大名楼的挂图,以及一台21英口寸的彩色电视机,多少彰显出主人的能干和富足。如果不是癌症拖垮了这个家庭,在这个举目都是低矮房屋的村子里,楼房主人的生活一定会让邻居羡慕。可现在,这间屋子里死气沉沉。

    “谁得病谁家就败。”王子清说,有钱的人家,病人能多活两天,没钱的,就只能等死了。他一再表示,村子里钱都不好借,有钱也不敢借出去。“我们东孙楼穷,不是因为人懒,没能力,而是因为癌症。只要家里有病人,都得花上三两万块。”

    王子清的一位叔叔患了胃癌,家里拿不出钱为他填这个无底洞,于是,在一个夜深人静的晚上,他上吊自杀了。“因没钱看病上吊自杀的,村里已有好几个。”他说。

    人死了,葬礼照样得办。火化1000元,棺材1500元,孝服500~800元,灵车灵棚各100元,唢呐100元。各种费用算下来,至少需要5000元。因外出和死亡的青壮年多,棺材都没人抬,以至于吊车下葬,成了当地一个“热门”行当。

    由于贫穷,村里如今盗窃成风。王子清家原来的围墙不高,人很容易翻墙进去。现在,他已把原有的围墙拆掉,准备加高。“我怕小偷。”他说。

    村头墙壁上,到处是触目惊心的治疗腹泻、癌症的广告。有些患者无奈只能找江湖游医寻求安慰;有些患者则只能求助“神”的力量。

    黄孟营村33岁的孔鹤琴,19岁嫁到此地,26岁得了直肠癌,4次手术,12次化疗,花了7 万多元,如今家徒四壁,外债高筑。两年前,“感到无望”的孔鹤琴皈依了基督。每周五,骨关节变形的她,都要坐在轮椅上,让丈夫推着,到两公里外的王寨村做礼拜。王寨村基督教堂的信徒芦美英则表示,七八十名教友中,基本都是身体有病的人。

    孙营村的村民孙振雨,不忍看着自己的乡亲们忍受癌症的折磨,于1999年,筹钱2500元,修复了村里的华佗庙,“初一十五,香火很旺”。但华佗庙重修后4年,孙的爱人得了偏瘫,至今卧病在床。可叹的是,连塑华佗像的匠人也因癌症而去世了。

    与周边“癌症村”一样,东孙楼村能参军的人很少。有几年,竟没有一个体检合格的。

    “人活得都没有希望了。”王子清说,“村里的人总担心,下一个死的会不会是自己。”


    又黑又臭的卫河在村口流过

    都是污染造的孽

    王子清家正对着一个大水塘,塘边有几棵树,几只白色的鸭子在水塘内戏水。如果不与癌症发生联系,塘边树下,静坐垂钓,本该是件惬意的事。

    像这样的水塘,几乎每个村子都有几处,只是有些已经变成了倾倒生活垃圾的地方。在这个西北高、东南低的县域内,因河流冲刷,坑塘较多。加之,上世纪50年代后期,当地人鼓足干劲,大修水利。干渠、支渠、斗渠、毛渠,通向每一个角落。四通八达的灌溉系统,造就了这个曾经的鱼米之乡。

    东孙楼村也有一个发达的灌溉系统。东南西北四条水渠,将村南两公里外的沙颍河水,输送到地里田间。这个处于沈丘县“锅底”的村子,即使在非灌溉季节,沟渠里也会积下不少的水。因此,这里几乎一年四季浸淫在水中。

    在王子清的记忆中,沙颍河和渠塘里的水曾经清澈透明。上世纪50年代,这里的人上地,“从来不带水”。渴了,随便在渠塘中掬一捧水就喝。“甜着呢,比现在的自来水都好喝”。

    可是,随着上世纪80年代末、90年代初,沿河一些污染工业项目纷纷上马,沙颍河水逐年开始变坏变臭,致使源自沙颍河的灌溉沟渠的水也变得腐臭难闻。在王子清的带领下,记者沿着村里的沟渠水塘走了一遍。水面上覆盖着一层绿油油的浮萍,树叶、秸秆散落其中,蚊虫猖獗,臭不可闻。

    据沈丘县水文站站长李斌提供的一份资料:沙颍河槐店(沈丘县城所在地)段,1990年、 1994年、2000年均属劣V类水质,已失去各种水体功能。中国环境监测总站公布的水质监测周报显示:2005年和2006年,此段水质分别有两次是Ⅳ 类水,其余时间皆为V类或劣V类;2007年第20周和21周的水质也均为劣V类。水体中高锰酸钾和氨氮含量均超Ⅲ类水标准的数倍。

    1994年7月中旬,淮河发生特大污染事故,在黑色污染团过后,水质有所好转的情况下,安徽省蚌埠市自来水公司取3000公升淮河水送到上海化验,结果对比美国环境保护机构公布的129种“首要控制污染物”,蚌埠三水厂和一水厂的源水分别查出90种和95种,其中,致癌物高达67种。

    淮河60%的来水量源自沙颍河,而沙颍河贯穿沈丘全境。今年4月,中国环境监测总站对沙颍河的监测报告显示,其水质全部为劣V类。

    “作为淮河最大支流,沙颍河的水质污染可想而知。”淮河卫士会长霍岱珊痛心地说。

    有研究显示,这些高污染、富含各种致癌物的水,通过发达的沟渠逐渐渗透到地下水系统,破坏了当地的地下水水质。据当地人反映,自1990年代以来,压水井里压出的水混浊不清,能看到明显的杂质;有些地方的水,粘性特别大,能像油一样流成细线状;水烧开后,水壶上会留有厚厚的一层水垢;即使开水也苦涩难咽,喝下去后喉咙会发麻。有媒体称,“地下50米以上的浅层地下水已不能饮用”。而在沈丘,农户自家的压水井一般都在20米以上。

    据阜阳市疾病预防控制中心2004年7月13日做出的一份水质检测报告显示:沈丘县黄孟营村一家的压井水送检样本中,有10多项指标超标,其中锰、硝酸盐氮严重超标。科学实验证明,过量摄入高硝酸盐氮的水或食物会引发消化道癌症或者肝癌,而高锰的暴露会对大脑产生危害,使一些大脑皮层坏死,对人的智力发育甚至大脑神经活动产生危害。

    “都是污染造的孽。”王子清说,虽然他本人不能从科学上证明沙颍河水污染和癌症之间的因果关系,但生活经验和直觉告诉他,严重污染的沙颍河是癌症高发的直接元凶。

    2005年,国家疾控中心曾对淮河流域癌症高发地进行全面普查,其中包括沈丘县全境,最终结论是:一、淮河流域沿河、近水区域癌症高发;二、癌症高发与劣Ⅴ类淮河水密切相关。

    而沈丘县卫生局一位副局长表示,虽然河水污染与癌症之间一定有关联,但关联究竟有多大,专家还在研究实验之中。

    希望政府搭建一个公众可以参与的平台

    沙颍河由沙河、颍河于周口汇流而成,自西向东流经河南中东部,全长600余公里,是淮河最大的支流。沈丘,是沙颍河入安徽的最后一站。

    “你们得利,俺们得病;你们升迁,俺们升天。”这是流传在沈丘县民间的一则顺口溜。主要针对的是坐落在沙颍河流域大大小小的企业,其中不少为高污染企业,比如造纸、皮革、塑料、酒类等等。这些企业中比较知名的有:项城市莲花味精股份有限公司(下称“莲花味精”)、扶沟县的扶沟味精厂、漯河的银鸽实业集团以及丁集皮革业等。过去这些企业的工业污水基本上都排放在了沙颍河中。据当地人反映,前些年,沙颍河水色如墨汁、臭气熏天,曾发生过呛死人事件。

    驸马沟是项城市污水入沙颍河的一条人工河道。其污水源主要是城市生活污水和莲花味精集团的工业废水。

    “以前这里臭气熏天,活像一个化肥窖,人人都得捂着鼻子走。”76岁的老船工连德财说。据老人描述,在没有污染之前,这里的水清澈见底,如果有什么东西掉到水里,岸上看得一清二楚。水里的鱼又多又大,经常能够捕到十几斤重的大鱼。而现在,这里的鱼几乎绝迹。即使偶尔能捕捞上一两条,也没人敢吃。在污染最严重的时候,“水里的鱼给什么喂,什么就死”。

    据说,前几年,水面上到处漂着白沫。有一次,不知何故,驸马沟的水面竟然着火了,把泄水闸烧坏了。

    “以前河里洗澡的人很多,每年都要淹死好几个人。”连德财说,“不过,水污染后也有好处,不会淹死人了。因为没人去游泳了。”

    仅这条小小的驸马沟,污水一流就是十几年,迟迟得不到治理。而早在1994年国家就启动了治淮工程,历经10年,投资600亿,但至今未能实现“水体还清”的目标。淮河干流支流的治污任务,依然十分严峻。

    “环保局是属地管理。很多污水都是从别处流到沈丘的,我们一点办法也没有。”9月5日,沈丘县环保局副局长徐启亮对记者说。沈丘县水文站站长也表达了水文站的无能为力。他说,水文站的权限只限于取样监测,并没有管理职能,因此,对治理帮不上真正的忙。他透露,有时地方政府出于利益保护,还会在一定程度上干涉水文站取水样。

    不过,在治污形成共识的今天,特别是在上级环保部门的重拳出击下,一些企业已经被迫加入治污的行列。比如位于项城市的莲花味精,这个昔日沙颍河的排污大户,曾屡被媒体曝光并被罚款,10多年被迫为环保交了7亿多元学费。项城市环保局前局长还因此“下课”,该市分管环

    保工作的副市长,也因此被责令深刻检查。

    “做好环保是首先对企业的效益负责。”莲花味精环保事业部负责人王飞对记者说,“我们企业曾经为环保付出过很惨痛的代价,几次差点被关闭,因此绝不能再走老路了。”

    “淮河卫士”会长霍岱珊,在沙颍河治污问题上曾屡次和莲花味精交涉,可谓“老对头”。今天他也认为,莲花味精“的确下了功夫改造自己”。

    “企业在主观上对自己负责了,在客观上就对社会负责了。”霍岱珊说,希望企业能够意识到,做好环保是分内的事,是真正对企业发展负责的事。

    少了莲花味精的污水排放,驸马沟的水质,在当地人看来,已经稍有好转,臭味也不像以前那么明显了。远远望去,几个老人坐在树荫下乘凉,卖冷饮的小贩在吆喝生意,更远处的沙颍河大桥下,几个孩子正在水中攀登桥墩。

    今天,最让霍岱珊头疼的不再是像莲花味精这样的大企业,而是许多名头不响的小企业。这些小企业经常和政府职能部门玩“猫捉老鼠”的游戏。如果仅仅依靠政府职能部门的人手,根本无法发现不达标的排放,更不用说治理了。“更何况,有些地方政府还会刻意保护本地的企业”。

    “政策宣传就像文工团。”霍岱珊说,“光有文工团不行,解决问题还要靠步兵,要打阵地战。”

    “公众就是步兵,就是打阵地战的基础。”霍岱珊说,“组织公众参与,可以形成对排污口的实时监控。”他一再呼吁,希望政府搭建一个公众可以参与的平台,让污染源无处隐身。

    命都没了,经济发展了又能怎样

    王子清家先后打过4口井,依次是4米、15米、28米、40米。

    井一次比一次打得深,但水的口感并没有明显变好。他自己也不知道这些水到底安全不安全,会不会让自家人远离癌症。每逢周末,在外工作的儿子和在城里上学的孙女,总会带些纯净水回来,这样的水,全家人才敢放心地饮用。

    3年前,在霍岱珊的联络下,亚洲博爱救助基金会为东孙楼村的全部村民安装了水过滤器。尽管“ 这些设备并不能根本改变水质”,但村民们还是松了口气。此后河南省财政每年安排4000万元专项资金,用于解决重污染地区群众饮水安全问题。沈丘县也在这一年修建了污水处理厂。同年,有关部门还为东孙楼村打了一口200米的深水井,并提供了主管道、潜水泵和无塔供水装置。入户管道需各家自行购买,为此,王子清花了800多元。

    2006年1月26日,即春节前三天,村里人第一次吃上了深井水。王子清把所有盛水的家伙都接得满满的,痛痛快快享受了一番。然而16天后,水停了,王子清获知的消息是试运行。4个月后,深井水正式供应。不过,“经常是三天两头没水”。

    今年8月初,深井水又停供。王子清只能重新饮用40米的压水井的水。可是,几天后,他的胃开始不适,“一天到晚折腾个没完”。其间,他的老伴过生日,因为没水,他只得用摩托车载着老伴,到沈丘县城过生日。

    “潜水泵的功率太小,负荷太大。周边几个村的水,都是接东孙楼的水,电机超负荷运转,烧坏了。”深井水管理员说,“潜水泵是水利局提供的,我到水利局找过,可水利局说没有配件。”

    这位管理员表示,自己曾和厂家联系过,但总也联系不上。“电机坏了,潜水泵就不能用了,得换新的”。

    然而换潜水泵又产生一个问题:“水利局不给换”,村里又没有钱,他不知道到哪儿去弄钱。“我也想过集资,但又怕集资违法。”管理员说,他只得挨家挨户做工作,一家收三五元,先把大功率的潜水泵买回来,以便尽早让村里人喝上“比较放心”的深井水。

    另一个让他头痛的问题是,1元/吨的水费,还不够付电费和管理费用。“实在没钱,那就只好断水了。”管理员摇了摇头。

    “以前,我最大的心愿就是村子里能打上一口深井。”王子清俯身望着井底,“可是三天两头断水,成本又那么高,以后我们该怎么办呢?”

    虽然深井水供得断断续续,可毕竟隔三岔五有了水吃。然而,有了深井水,并不意味着癌症病魔从此就远离了这个村庄。霍岱珊以前联系过的一些癌症患者,相继都离开人世,现在不断仍有新的患者的名字传入他的耳朵。

    王子清也常常担心,活着的人会有各种隐疾。他希望有关部门能为村里的人做一些专项检查,建立村民医疗档案,并派专人检验一下深井水的水质,是否真正得到了改善。

    虽然新农村建设的口号,在东孙楼村喊的与外面的世界一样响,但老王真诚希望“不要仅仅是一阵风,仅仅是应付检查”,而是能确实给村里改善一下环境,比如道路硬化,渠塘净化,等等。

    “命都没了,经济发展了又能怎样?”王子清质问。

    对于东孙楼村出现的新问题及群众呼声,沈丘县卫生局一位副局长称,有关部门曾做出过应对措施,但这些应对措施还没有公开,不便透露。

    不过,霍岱珊倒是带来一条好消息:一位日籍华裔将为村里提供生物过滤技术,据说应用此技术,届时就可喝上“达标”的水。

    “莲花味精总经理高君也说了,他们公司将积极对待沙颍河的污染问题,要为历史的欠债填坑(埋单)。”霍岱珊说。

    尽管东孙楼村治污的脚步在加快,可村子外的污染仍在继续。途经纸店镇一带,依然可闻到刺鼻的气味。司机介绍说,那一带的皮革厂很多,污染特别严重,许多树都死了。记者沿途看到,沟渠里的水黑乎乎的,像是粘稠的发酵物,沟渠边的几行树,叶子已经完全脱落,毫无生机。

    一只野鸭子在不远处的水塘里游弋,司机放慢车速,瞟了一眼。

    “这年头,能看到野鸭子可不是一件容易的事。”他说。

    就在SIGGRAPH大会刚结束之后,AMD和暴雪在AMD官方网站上放出了《星际争霸II》的官方技术文档,通过游戏引擎技术的展示让星际迷们感受到越来越多的惊喜。

    画面优化给CPU带来考验
    着色方面,在使用原型的基础上利用3D Studio MAX让程序员对整体效果做最大的优化,反复的提炼使得整个作品就像被艺术家精心雕琢一样,配合上8000个独特的、不重复的线性着色渲染代码,使得Starcraft II与早期的游戏代码相比增加了N倍。

            在游戏制作初期,暴雪就认识到GPU的强大性能,因此在设计的时候就采用了以GPU为主的优化和大幅度图像质量增加。

            由于使用大量像素渲染操作,如果在游戏中当你选择了控制成百上千的zerglings或者marines等作战单位集体行动,势必造成CPU超负荷,即便是强大的GPU这也是一个考验。
    大量的视觉特效(Based Effects)
            包括FP16 HDR、光线散射/反射效果(Diffuse and specular for lighting)、景深效果(depth of field)、体积雾(fog volumes)、动态环境遮蔽(dynamic ambient occlusion)、智能贴图置换(smart displacement)等等,这些都是Starcraft II的“BT”之处。

            然而,作为一个即时战略游戏,暴雪依然在“故事情节”的设定上下足了功夫,在游戏引擎的影响下,延迟缓冲器的深度和平均值,包括着色组建都完全为了效果而服务。而照明管道和阴影的利用也让深景效果等一些特效表现得淋漓尽致。不过,这些特效都还在讨论中。
    在早期的开发过程中,图形引擎也作为一个重点中的重点进行设计:
    可扩展性第一
            对于游戏引擎来说,一个主要的设计目标是扩展的引擎。暴雪的游戏质量是人所共知的,不光是他们的能力,更重要的是经验,从以前的一系列游戏开发经验来说,整个游戏各种族之间能力的平衡问题非常重要,而且图像效果之间的兼容性也必须考虑到,这样也是为了各种不同平台硬件之间搭配的玩家都能体验这个游戏的魅力所在,以确保游戏有足够的竞争力,因而从ATI Radeon 9800/NVIDIA GeForce FXs系列到ATI Radeon HD 4800s和NVIDIA GeForce G200s都可以轻松的进行游戏。
            相比之下,GPU的负载才是重点,像素着色单元往往会因为大量的单位同时出现而使得GPU难以承受,顶点着色单元的运用也必须合理,而先进的GPU才能体验到最大限度的特效,而对于普通的GPU只能尽量减少使用率来保证游戏的流畅度。
    一个引擎,两个“世界”(Dual Nature of the Engine)

            这个双重性质的引擎使得整个游戏有更完美的体验,其实就是说SC2具有两个视角模式,一个是普通的RTS视角、一个是单人模式的RPG视角,当然在这两个模式下呈现的画面效果是有所不同的。具备两个视觉模式的原因正是我们之前提到的故事模式,当切换到单人模式的RPG视角时,玩家将会体验到暴雪对于整个游戏设定的精妙之处,通过一系列互动,包括对话等手段,其实从某种感觉上来说更像是第一人称射击游戏(FPS)。
        暴雪还将对这些不同的设定做更为细致的讨论,这样才能让玩家体验到不同的技术效果的运用。
    屏幕基础效果
            作为Starcraft II的另一个设计目标,照明环境模式让游戏的互动更多,之前在魔兽争霸三中,每个单位有一个硬性限制,有多少亮度,可能会影响它在任何特定时间。出于这个原因,使用动态照明是相当微不足道的,但是在Starcraft II中每个单位有一个照明设备,当其中一个开启之后其他也会开启,这样效果非常好,但是由此带来的地图的设计和绘制,包括地图地形切片就成了相当复杂的问题。而即便是解决了这些,给GPU带来的负荷也是巨大的。
            针对于此,暴雪也做了很多限定 :
            ◆ 颜色组成部分,并不受当地照明,如发射、环境地图和点燃前期的彩色组成部分;
            ◆ 深度;
            ◆ 每像素正常;
            ◆ 如果使用静止环境闭塞,出口的环境闭塞的纹理被忽略,如果屏幕空间环境闭塞启用;
            ◆ 亮灯的弥漫物质的颜色;
            ◆ 亮灯的镜面材料的颜色。

    解码缓冲器
            所有的缓冲器应该都使用相同的深度,不幸的是似乎这些缓冲器远远不能满足暴雪的需要,这些缓冲器以24字节每像素推动输出带宽,这样使得整个带宽明显不足,因此在安置光源的时候必须牺牲一些。
            为了向MRTs提供每像素值以保证用于各种的效果,因而以下必不可少:
            ◆ 深度值为照明、雾卷、动态环境闭塞和智能景深,预测,边缘检测和厚度测量;
            ◆ 平均值为动态环境闭塞;
            ◆ 弥漫性及镜面照明。
    延迟渲染
            在Starcraft II中延迟渲染只适用与当前的渲染,包括灯光的点和散射出去的面都要再渲染,但是由于游戏中会出现很多的照明设备,如果都使用这样的方法渲染的话一定会造成画面过于缓慢,因此延迟缓冲器的出现也解决了这一难题。在延迟渲染和着色方程式的帮助下计算机能很快的绘制出不同形式或者更为复杂的光源,这样也为其他的计算提供了一个后处理的过程。
    像素坐标重建

            受益于早期的样板,暴雪在设计新的形状和色彩的时候轻松了不少,而且同时还释放了CPU。

            相机原理我们都知道了,就通过这样的模式使得暴雪在视觉上更了解该如何设计。

    屏幕空间环境光遮蔽
            屏幕空间环境光遮蔽(Space Ambient Occlusion,简称SSAO)。如果对这个名词感到陌生,那提及到让大家记忆犹深的《Crysis》逼真的光照效果时,一切就很简单了,这就是SSAO技术的独到之处。
            暴雪在这使用这方案的时候考虑到了整个空间的效果和质感,也许从某写方面上来看,没这个必要,但是基于暴雪的理念,一点点瑕疵都是不被允许的。

            在任何可见的点和表面在屏幕上,采取多个样本(8至32),这些样本,表现了在三维空间中从目前的点计算,到预计回屏幕空间所需的时间和深度。

            现在的目的是检查是否深入采样点,如果接近这个更远的采样点本身能获取信息的话就这么做下去,这是一个函数问题,因此需要复杂的计算。


            不过在对地图的计算时不会采用这样的方法,这仅仅是为了形成一个空间的效果而已。
    模糊效果

            前面的一切都是为了更加细致的描绘出所有的场景,但是,有时候游戏还是需要一些模糊的图形图像,如果一直使用SSAO技术的话,必然导致不真实。
            对于SSAO来说,虽然是一种优势,但是高智能的判断在这时也显得尤为重要,何时需要高精度的细致图像,何时需要动态模糊效果,无论是着色还是渲染都需要大量的资源的计算才能显现这些复杂的效果。
    自我闭塞

            这也是一个相当复杂的效果,就像一个围绕着一个半球体产生偏移向量,这一点上在屏幕上(这意味着需要使用大量的延迟缓冲器),由一个矩阵转化每个偏移向量和正常向量来抵销矢量。
    边缘处理
            偏移向量在空间中并不像在屏幕空间中一样,由于镜头的不断移动使得SSAO不断变化,这样就需要在移动的过程中对镜头的边缘进行处理,以保证完美的视觉效果,但是纹处理包装上并不是一件简单的事。
    SSAO的表现
            SSAO提供的效果是让人相当满意的,视觉图像质量的处理近乎完美,但是成本过高,而且大量使用还是会造成系统性能瓶颈,这样得不偿失,因此合理的使用这个技术也是讨论的问题之一。
    SSAO和整体光线效果
            从图中很明显的发现了SSAO的精妙之处,如此逼真的效果让人赞叹不已,其实更多的还是其后面的技术支撑。



        我们可以把一切制约因素一起使用以下过程表示:
            ◆为每个像素在源的形象和存储执行一个全屏幕通过计算混乱循环结果,在Alpha通道的每边缩减一个CoC图像缓冲区的四分之一大小;
            ◆产生中等模糊图像通过应用的RGB高斯模糊与每个样本对源图像加权CoC;
            ◆产生的最大模糊图像只有缩小的RGB的图像缓冲区源图像的四分之一,每一个CoC和大型模糊缓冲器可以同时使用不同的通道;
            ◆最高模糊图像与RGB样本加权由缩减CoC执行,Alpha通道中载有CoC,也有模糊,但其样本不加权本身;
            ◆缩小和模糊一张深度地图成为一个缩减深度图像,重用深入SSAO缩减(SSAO不模糊深度 );
            ◆然后开始最后的景深着色,有一定的形象来源,中等和大型模糊、模糊CoC的形象,非模糊深入地图和缩减深入形象的渲染。



            景深渲染包括:
            ◇计算小模糊价值,直接使用小样本着色四近邻像素;
            ◇计算CoC的像素(缩减CoC将不匹配);
            ◇样本非模糊,使用模糊的深入比较——计算机CoC,如果是模糊的深入,比非模糊的深入更远,否则使用CoC价值样本,模糊CoC的形象;
            ◇计算贡献,从每一个可能的模糊图象,计算小模糊的颜色,中型和大型的图像模糊的基础上,CoC的因素;
            ◇小型,中型和大型模糊;
            ◇输出Alpha包括的源(无模糊)形象。
    处理透明物体渲染
            透明度是一个值得深入研究的问题,当涉及到开发时暴雪的董事会认为延迟渲染技术不支持透明度的话就应该在其他地方加上这个功能,而事实证明这是一个正确的选择。
            作为是典型的就是延迟绘制技术,透明度的问题还设计到树荫等一系列问题,因此必须选择一些有代表性的物体来标记之后再做探讨。光线的问题在透明度的谈论中再一次被提及,因为这是个相辅相成的东西,因此暴雪在制作这一切的时候都努力的寻找解决方案。
    半透明阴影(Translucent Shadows)
            这种技术能令烟雾、爆炸也能投射出阴影效果。

            早期在建模的时候影子系统都是成功地利用屏幕空间信息解决跟踪问题,否则将会困难很多,但这次暴雪将显示如何延长阴影地图的每像素信息与一些额外的信息通道,可以用来容易的充实阴影,地图上实现半透明的阴影支持。
            阴影地图算法是延长与第二地图的阴影信息形成半透明的阴影,大部分地图的影子仍然会包含不透明信息的阴影,此外还需要做到在颜色的缓冲区发现颜色半透明的阴影。不过,这一切都需要硬件的支持,如果硬件不支持将会有一个空的颜色,因此在考虑到深景等特效的时候暴雪依然在处理阴影效果。
            首先是清理,以白色调和,然后填补渲染透明物体的阴影,就这样形成一个透明的阴影色彩缓冲区,再加上测试,处理这些透明光过滤器,最后形成需要的效果。
    光过滤处理器

    总结:
            从上面简短的一份官方技术文档(因为AMD只放出这一部分),我们已经可以了解到暴雪在SC2这个游戏上所付出的努力和贡献了,其效果之华丽恐怕也是惊人的。作为暴雪十多年磨一剑的大作,《星际争霸II》可是包含了现在最尖端的技术和效果,无论是暴雪的质量还是技术相信都是世界顶级的水平,这样的游戏公司确实在为自己的的作品留芳百世而努力!不过,至于还要等多久,至今仍是一个谜。

    NDIS HOOK是专业级防火墙使用的一种拦截技术,NDIS HOOK的重点是如何获得特定协议对应NDIS_PROTOCOL_BLOCK指针,获得了该指针,接下来就可以替换该协议所注册的收发函数,而达到拦截网络数据的目的。
         获 得NDIS_PROTOCOL_BLOCK指针的方法一般是用NdisRegisterProtocol注册一个新的协议,所获得的协议句柄实际上就是一 个NDIS_PROTOCOL_BLOCK指针,顺着该指针遍历NDIS_PROTOCOL_BLOCK链表,就可以找到你所要挂钩的协议所对应的 NDIS_PROTOCOL_BLOCK.之所以可以这样做,是因为每注册一个协议,系统都会把该协议对应的NDIS_PROTOCOL_BLOCK放置 在协议链表的开头,该协议链表每个元素都是NDIS_PROTOCOL_BLOCK类型,代表一个已经注册的协议。
         事 实上我们需要的只是TCPIP协议族的NDIS_PROTOCOL_BLOCK指针,毕竟TCP,IP,ARP,ICMP等等几乎所有我们感兴趣的协议, 都是在tcpip.sys协议驱动里面实现的。如果我们只需要TCPIP协议所对应的NDIS_PROTOCOL_BLOCK,那么上面的方法就有点繁琐 了。我们可以试着寻找更简便的方法来获得TCPIP协议的NDIS_PROTOCOL_BLOCK.
    于 是我对tcpip.sys驱动进行了反汇编,发现NDIS_PROTOCOL_BLOCK指针存放在一个名为_ARPHandle的全局变量里面,所以如 果能找到_ARPHandle的地址,我们就成功了,我们完全可以把该全局变量的偏移量作为一个常量来使用,但这里纯粹为了拓宽思路,我介绍另一种找到该 全局变量的方法。
         Tcpip.sys有个导出函数叫IPDelayedNdisReEnumerateBindings,该函数内部曾经出现过_ARPHandle 的地址,为什么会出现它的地址呢,因为该函数内部调用过NdisReEnumerateProtocolBindings函数,懂得反汇编的应该知道,在 用call指令调用函数之前,必然会用到push指令将函数的参数压到栈里面去,不巧的是, NdisReEnumerateProtocolBindings函数只有一个参数,而该参数恰恰是一个NDIS_PROTOCOL_BLOCK指针类 型,在这里,实际上就是把_ARPHandle当作参数传给了
    NdisReEnumerateProtocolBindings,所以_ARPHandle的地址必然会出现在push指令的后面,说具体一点,紧跟push指令的四个字节就是_ARPHandle的地址。
          所以具体的思路就是这样,先找到IPDelayedNdisReEnumerateBindings函数的地址,然后从该函数的地址开始搜索push指令的特征码,搜到了以后,把紧跟push指令的四个字节作为指向NDIS_PROTOCOL_BLOCK指针的指针返回。
         也许有的人会问,如果IPDelayedNdisReEnumerateBindings函数体内部出现过多次push指令,岂不是会搜出不正确的地址,事实上,虽然都叫push指令,然而在机器码级别是不同的,push指令的机器码表示有十几种之多,用来区别不同的寻址方式,调用NdisReEnumerateProtocolBindings 时用的push指令字节序列是0xff35,这个push指令表示后面紧跟的四个字节是一个内存地址,而不是一个立即数或者寄存器之类的。知道了这些,我 们就可以清楚,在一个有限的地址范围,0xff35的唯一性是可以得到满足的。根据我的观察,在win2000,winxp,win2003上面,IPDelayedNdisReEnumerateBindings本身是一个很短的函数,0xff35指令确实只出现过一次,所以该方法是很可靠的。
    思路已经出来了,下面我把详细的代码给大家贴出来,理解这些代码需要对windows Pe格式有所了解,如果你不想理解也行,代码可以直接拿来用。
           以下是我写的一个 获取内核模块某个导出函数地址的 通用例程。这里主要是为了获取tcpip.sys模块的导出函数IPDelayedNdisReEnumerateBindings
       void* GetRoutineAddress(char* ModuleName,char* RoutineName)
    {
           PIMAGE_DOS_HEADER dos_hdr;
        PIMAGE_NT_HEADERS nt_hdr;
        PIMAGE_EXPORT_DIRECTORY export_dir;
        ULONG *fn_name, *fn_addr, i;
          char* base;
          base=(char*)FindModule(ModuleName);//该函数用来获得内核模块的基地址
        if(!base)
            return NULL;
        DbgPrint("tcpip address:%p",base);
        dos_hdr = (PIMAGE_DOS_HEADER)base;
        if (dos_hdr->e_magic != IMAGE_DOS_SIGNATURE)
            return NULL;
        nt_hdr = (PIMAGE_NT_HEADERS)(base + dos_hdr->e_lfanew);
        export_dir = (PIMAGE_EXPORT_DIRECTORY)(base + nt_hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        fn_name = (ULONG *)(base + export_dir->AddressOfNames);
        fn_addr = (ULONG *)(base + export_dir->AddressOfFunctions);
        for (i = 0; i < export_dir->NumberOfNames; i++, fn_name++, fn_addr++)
        {
            if (strcmp(RoutineName, base + *fn_name) == 0)
            {
                return base + *fn_addr;
            }
        }
        return NULL;
    }
    以下是FindModule函数的实现:
    void *
    FindModule(char *name)
    {
        ULONG i, n, *q;
        PSYSTEM_MODULE_INFORMATION p;
        void *base;
        ZwQuerySystemInformation(SystemModuleInformation, &n, 0, &n);
        q = (ULONG *)ExAllocatePool(PagedPool, n);
        ZwQuerySystemInformation(SystemModuleInformation, q, n * sizeof (*q), 0);
        p = (PSYSTEM_MODULE_INFORMATION)(q + 1);
        base = NULL;
        for (i = 0; i < *q; i++) {
            if (_stricmp(p.ImageName + p.ModuleNameOffset, name) == 0) {
                base = p.Base;
                break;
            }
        }
        ExFreePool(q);
        return base;
    }
    以下是获取tcpip协议的NDIS_PROTOCOL_BLOCK指针的函数
       void* GetProtocolBlock()
    {
        char* base;
        char bytes[]={0xff,0x35};
        base=GetRoutineAddress("tcpip.sys","IPDelayedNdisReEnumerateBindings");
         while(RtlCompareMemory(base,bytes,2)!=2)
        {
           base++;
        }
        return **((void***)(base+2));
    }

    ---INLINE HOOK实现NDIS HOOK
    前面讲述了如何通过获取NDIS_PROTOCOL_BLOCK来实现NDIS HOOK,这里讲述第二种方法,那就是inline hook方法。说起inline hook,也不是什么新鲜玩意,无非是在一个函数的首部嵌入一个jmp机器指令,在该函数执行有效代码前就跳到我们的代理函数,在我们的代理函数里做了必要的处理以后,再跳回原来的函数,接着执行原函数的指令。
    既然tcpip.sys是标准的NDIS协议驱动,那么收包函数显然应该是在tcpip.sys内部实现的,我们直接找到这两个收包函数,然后对其inline hook不就可以了吗?经过逆向分析,我找到了这两个函数,本人安装了两个XP系统,其中一个导出了这两个函数,另一个系统却没导出,所以我们仍然需要用特征码搜索这两个函数,这两个函数声明如下:
    NDIS_STATUS
    ARPRcv (NDIS_HANDLE BindContext,
    NDIS_HANDLE MacContext,
    UCHAR* HeadBuffer,
    ULONG HeadSize,
    UCHAR* Buffer,
    ULONG BufferSize,
    ULONG PacketSize);
    INT
    ARPRcvPacket (NDIS_HANDLE BindContext,
    PNDIS_PACKET Packet);
    搜索这两个函数地址的代码如下:
    //以下全局变量保存两个函数的地址
    void* ARPRcv=NULL;
    void* ARPRcvPacket=NULL;
    void SearchProtocolRoutine()
    {
    //以下分别为两个收包函数的特征码
    UCHAR ARPRcvBytes[] ={0x8b,0xff,0x55,0x8b,0xec,0x56,0x8b,0x75,0x08,0x33};
    UCHAR ARPRcvPacketBytes[]={0x8b,0xff,0x55,0x8b,0xec,0x51,0x53,0x56,0x57,0x8b};
    //获取tcpip.sys模块的基地址,该函数在前一节已经提供给大家
    char* base=FindModule("tcpip.sys");
    while(ARPRcv==NULL||ARPRcvPacket==NULL)
    {
    if(ARPRcv==NULL&&
    RtlCompareMemory(ARPRcvBytes,base,10)==10)
    {
    ARPRcv=base;
    }
    else if(ARPRcvPacket==NULL&&
    RtlCompareMemory(ARPRcvPacketBytes,base,10)==10)
    {
    ARPRcvPacket=base;
    }
    base++;
    }
    }
    各种编译器所编译的函数,前几个指令都是几乎一样的,用来建立堆栈帧,这些指令叫函数的序言。
    在win2000上是三字节
    push ebp
    mov ebp, esp
    到了winxp以及后续系统上,则变成了五字节
    mov edi, edi
    push ebp
    mov ebp, esp
    而一个近跳转指令刚好是五字节,在xp上刚好覆盖了函数的序言,所以在XP上挂钩也相对容易一点,这里着重说明如何对ARPRcv进行挂钩,我们在ARPRcv内部插入一个jmp指令,将跳到ARPRcvProx函数,该函数是个裸函数,函数实现如下:
    _declspec(naked) ARPRcvProx()//跳板函数
    {
    _asm
    {
    mov edi, edi
    push ebp
    mov ebp ,esp
    //七个参数开始压栈
    push [ebp+20h]
    push [ebp+1ch]
    push [ebp+18h]
    push [ebp+14h]
    push [ebp+10h]
    push [ebp+0ch]
    push [ebp+8]
    call NewARPRcv //调用NewARPRcv函数
    cmp eax,0x10003 //判断函数返回值是否NDIS_STATUS_NOT_ACCEPTED
    jz end //如果是NDIS_STATUS_NOT_ACCEPTED,直接结束本函数
    //而不跳回到ARPRcv函数
    mov eax,ARPRcv //如果返回的不是NDIS_STATUS_NOT_ACCEPTED,将会
    //执行到这条指令,该指令将 ARPRcv函数的地址装入eax
    add eax,5 //将ARPRcv地址值加上5,存入eax,表示即将跳转的//地址
    jmp eax //开始跳回ARPRcv体内
    end:
    pop ebp
    retn 1ch
    }
    }
    在该函数内部,又调用了NewARPRcv函数,原型和ARPRcv保持一致,也必须由我们自己实现:
    NDIS_STATUS
    NewARPRcv(
    IN NDIS_HANDLE ProtocolBindingContext,
    IN NDIS_HANDLE MacReceiveContext,
    IN PVOID HeaderBuffer,
    IN UINT HeaderBufferSize,
    IN PVOID LookAheadBuffer,
    IN UINT LookaheadBufferSize,
    IN UINT PacketSize
    )
    {
    /*
    在这里加入你的判断逻辑代码,是否拦截该数据
    如果要拦截,则返回 NDIS_STATUS_NOT_ACCEPTED
    否则返回NDIS_STATUS_SUCCESS,把数据交给ARPRcv处理
    */
    return NDIS_STATUS_SUCCESS;
    }
    同样的原理,我们在ARPRcvPacket里面插入jmp指令,将跳转到ARPRcvPacketProx裸函数,该函数实现如下:
    _declspec(naked) ARPRcvPacketProx()
    {
    _asm
    {
    mov edi, edi
    push ebp
    mov ebp ,esp
    //两个参数开始压栈
    push [ebp+0ch]
    push [ebp+8]
    call NewARPRcvPacket//调用NewARPRcvPacket
    cmp eax,0 //如果返回0则表示拒绝该数据包
    jz end //直接返回本函数
    mov eax ,ARPRcvPacket
    add eax ,5
    jmp eax //跳回ARPRcvPacket函数第六个字节
    end: pop ebp
    retn 8
    }
    }
    在该函数内部,将会调用NewARPRcvPacket,函数实现如下:
    INT
    NewARPRcvPacket(NDIS_HANDLE BindContext,
    PNDIS_PACKET ndisPacket)
    {
    /*
    在这里加入你的判断逻辑,是否拦截该数据,如果要拦截,则返回0,
    否则返回非0
    */
    DbgPrint("RcvPacket");
    return 1;
    }
    请仔细阅读以上代码的注释,接下来,我们还必须提供一个函数实现安装和卸载挂钩功能
    void PatchARPRcv(BOOLEAN isPatch)//isPatch为TRUE表示安装挂钩,为FALSE表示卸载挂钩。
    {
    /*即将用以下五个字节覆盖ARPRcv函数前五个字节
    这5个字节就是jmp XXXX指令的机器码,因为跳转的相对地址还需要
    进一步计算,所以暂时用零填充
    */
    UCHAR patchBytes[5]={0xe9,0x00,0x00,0x00,0x00};
    //即将用以下五个字节覆盖ARPRcvPacket函数前五个字节
    UCHAR patchBytes2[5]={0xe9,0x00,0x00,0x00,0x00};
    //保存原始函数的前五个字节,方便以后恢复挂钩
    UCHAR restoreBytes[5]={0x8b,0xff,0x55,0x8b,0xec};
    /*
    以下两行代码计算跳转的偏移量
    */
    int offset=(char*)ARPRcvProx-(char*)ARPRcv-5;
    int offset2=(char*)ARPRcvPacketProx-(char*)ARPRcvPacket-5;
    //修正patchBytes和patchBytes2中的相对地址
    memcpy(patchBytes+1,&offset,4);
    memcpy(patchBytes2+1,&offset2,4);
    if(isPatch)
    {
    DisableWriteProtect();//禁止写保护
    memcpy(ARPRcv,patchBytes,5);
    memcpy(ARPRcvPacket,patchBytes2,5);
    EnableWriteProtect(); //开启写保护
    }
    else
    {
    DisableWriteProtect();
    memcpy(ARPRcv,restoreBytes,5);
    memcpy(ARPRcvPacket,restoreBytes,5);
    EnableWriteProtect();
    }
    }
    因为ARPRcv和ARPRcvPacket函数处于只读页,所以必须先禁用写保护才能向其中插入代码,禁用写保护和开启写保护代码如下:
    void
    DisableWriteProtect()
    {
    _asm{
    cli
    mov eax, cr0
    and eax, 0FFFEFFFFh
    mov cr0, eax
    }
    }
    void
    EnableWriteProtect()
    {
    _asm{
    mov eax, cr0
    or eax, not 0FFFEFFFFh
    mov cr0, eax
    sti
    }
    }
    注意这些代码暂时只适用XP系统,在win2000和win2003上都需要少许改动。

    作者:小马丁·路德·金
      今天,我高兴地同大家一起,参加这次将成为我国历史上为了争取自由而举行的最伟大的示威集会。
      100年前,一位伟大的美国人——今天我们就站在他象征性的身影下——签署了《解放宣言》。这项重要法令的颁布,对于千百万灼烤于非正义残焰中的黑奴,犹如带来希望之光的硕大灯塔,恰似结束漫漫长夜禁锢的欢畅黎明。
      然而,100年后,黑人依然没有获得自由。100年后,黑人依然悲惨地蹒跚于种族隔离和种族歧视的枷锁之下。100年后,黑人依然生活在物质繁荣翰海的贫困孤岛上。100年后,黑人依然在美国社会中间向隅而泣,依然感到自己在国土家园中流离漂泊。所以,我们今天来到这里,要把这骇人听闻的情况公诸于众。
      从某种意义上说,我们来到国家的首都是为了兑现一张支票。我们共和国的缔造者在拟写宪法和独立宣言的辉煌篇章时,就签署了一张每一个美国人都能继承的期票。这张期票向所有人承诺——不论白人还是黑人——都享有不可让渡的生存权、自由权和追求幸福权。
      然而,今天美国显然对她的有色公民拖欠着这张期票。美国没有承兑这笔神圣的债务,而是开始给黑人一张空头支票——一张盖着“资金不足”的印戳被退回的支票。但是,我们决不相信正义的银行会破产。我们决不相信这个国家巨大的机会宝库会资金不足。
      因此,我们来兑现这张支票。这张支票将给我们以宝贵的自由和正义的保障。
      我们来到这块圣地还为了提醒美国:现在正是万分紧急的时刻。现在不是从容不迫悠然行事或服用渐进主义镇静剂的时候。现在是实现民主诺言的时候。现在是走出幽暗荒凉的种族隔离深谷,踏上种族平等的阳关大道的时候。现在是使我们国家走出种族不平等的流沙,踏上充满手足之情的磐石的时候。现在是使上帝所有孩子真正享有公正的时候。
      忽视这一时刻的紧迫性,对于国家将会是致命的。自由平等的朗朗秋日不到来,黑人顺情合理哀怨的酷暑就不会过去。1963年不是一个结束,而是一个开端。
      如果国家依然我行我素,那些希望黑人只需出出气就会心满意足的人将大失所望。在黑人得到公民权之前,美国既不会安宁,也不会平静。反抗的旋风将继续震撼我们国家的基石,直至光辉灿烂的正义之日来临。
      但是,对于站在通向正义之宫艰险门槛上的人们,有一些话我必须要说。在我们争取合法地位的过程中,切不要错误行事导致犯罪。我们切不要吞饮仇恨辛酸的苦酒,来解除对于自由的饮渴。
      我们应该永远得体地、纪律严明地进行斗争。我们不能容许我们富有创造性的抗议沦为暴力行动。我们应该不断升华到用灵魂力量对付肉体力量的崇高境界。
      席卷黑人社会的新的奇迹般的战斗精神,不应导致我们对所有白人的不信任——因为许多白人兄弟已经认识到:他们的命运同我们的命运紧密相连,他们的自由同我们的自由休戚相关。他们今天来到这里参加集会就是明证。
      我们不能单独行动。当我们行动时,我们必须保证勇往直前。我们不能后退。有人问热心民权运动的人:“你们什么时候会感到满意?”只要黑人依然是不堪形容的警察暴行恐怖的牺牲品,我们就决不会满意。只要我们在旅途劳顿后,却被公路旁汽车游客旅社和城市旅馆拒之门外,我们就决不会满意。只要黑人的基本活动范围只限于从狭小的黑人居住区到较大的黑人居住区,我们就决不会满意。只要我们的孩子被“仅供白人”的牌子剥夺个性,损毁尊严,我们就决不会满意。只要密西西比州的黑人不能参加选举,纽约州的黑人认为他们与选举毫不相干,我们就决不会满意。不,不,我们不会满意,直至公正似水奔流,正义如泉喷涌。
      我并非没有注意到你们有些人历尽艰难困苦来到这里。你们有些人刚刚走出狭小的牢房。有些人来自因追求自由而遭受迫害风暴袭击和警察暴虐狂飙摧残的地区。你们饱经风霜,历尽苦难。继续努力吧,要相信:无辜受苦终得拯救。
      回到密西西比去吧;回到亚拉巴马去吧;回到南卡罗来纳去吧;回到佐治亚去吧;回到路易斯安那去吧;回到我们北方城市中的贫民窟和黑人居住区去吧。要知道,这种情况能够而且将会改变。我们切不要在绝望的深渊里沉沦。
      朋友们,今天我要对你们说,尽管眼下困难重重,但我依然怀有一个梦。这个梦深深植根于美国梦之中。
      我梦想有一天,这个国家将会奋起,实现其立国信条的真谛:“我们认为这些真理不言而喻:人人生而平等。”
      我梦想有一天,在佐治亚州的红色山岗上,昔日奴隶的儿子能够同昔日奴隶主的儿子同席而坐,亲如手足。
      我梦想有一天,甚至连密西西比州——一个非正义和压迫的热浪逼人的荒漠之州,也会改造成为自由和公正的青青绿洲。
      我梦想有一天,我的四个小女儿将生活在一个不是以皮肤的颜色,而是以品格的优劣作为评判标准的国家里。
      我今天怀有一个梦。
      我梦想有一天,亚拉巴马州会有所改变——尽管该州州长现在仍滔滔不绝地说什么要对联邦法令提出异议和拒绝执行——在那里,黑人儿童能够和白人儿童兄弟姐妹般地携手并行。
      我今天怀有一个梦。
      我梦想有一天,深谷弥合,高山夷平,歧路化坦途,曲径成通衢,上帝的光华再现,普天下生灵共谒。
      这是我们的希望。这是我将带回南方去的信念。有了这个信念,我们就能从绝望之山开采出希望之石。有了这个信念,我们就能把这个国家的嘈杂刺耳的争吵声,变为充满手足之情的悦耳交响曲。有了这个信念,我们就能一同工作,一同祈祷,一同斗争,一同入狱,一同维护自由,因为我们知道,我们终有一天会获得自由。
      到了这一天,上帝的所有孩子都能以新的含义高唱这首歌:
      我的祖国,可爱的自由之邦,我为您歌唱。这是我祖先终老的地方,这是早期移民自豪的地方,让自由之声,响彻每一座山岗。
      如果美国要成为伟大的国家,这一点必须实现。因此,让自由之声响彻新罕布什尔州的巍峨高峰!
      让自由之声响彻纽约州的崇山峻岭!
      让自由之声响彻宾夕法尼亚州的阿勒格尼高峰!
      让自由之声响彻科罗拉多州冰雪皑皑的洛基山!
      让自由之声响彻加利福尼亚州的婀娜群峰!
      不,不仅如此;让自由之声响彻佐治亚州的石山!
      让自由之声响彻田纳西州的望山!
      让自由之声响彻密西西比州的一座座山峰,一个个土丘!
      让自由之声响彻每一个山岗!
      当我们让自由之声轰响,当我们让自由之声响彻每一个大村小庄,每一个州府城镇,我们就能加速这一天的到来。那时,上帝的所有孩子,黑人和白人,犹太教徒和非犹太教徒,耶稣教徒和天主教徒,将能携手同唱那首古老的黑人灵歌:“终于自由了!终于自由了!感谢全能的上帝,我们终于自由了!”
      □□
      马丁·路德·金(公元1929—1968年),美国黑人律师,著名黑人民权运动领袖。一生曾三次被捕,三次被行刺,1964年获诺贝尔和平奖。1968年被种族主义分子枪杀。他被誉为近百年来八大最具有说服力的演说家之一。1963年他领导25万人向华盛顿进军“大游行”,为黑人争取自由平等和就业。马丁·路德·金在游行集会上发表了这篇著名演说。
      (bob录自中国文史出版社《世纪档案》)----------------------------
      附:英文原文----------------------------i have a dreamby martin luther king, jr.delivered on the steps at the lincoln memorial in washingtond.c. on august 28, 1963five score years ago, a great american, in whose symbolic shadowwe stand signed the emancipation proclamation. this momentousdecree came as a great beacon light of hope to millions of negroslaves who had been seared in the flames of withering injustice.it came as a joyous daybreak to end the long night ofcaptivity.but one hundred years later, we must face the tragic fact thatthe negro is still not free. one hundred years later, the lifeof the negro is still sadly crippled by the manacles ofsegregation and the chains of discrimination. one hundred yearslater, the negro lives on a lonely island of poverty in themidst of a vast ocean of material prosperity. one hundred yearslater, the negro is still languishing in the corners of americansociety and finds himself an exile in his own land. so we havecome here today to dramatize an appalling condition.in a sense we have come to our nation's capital to cash a check.when the architects of our republic wrote the magnificent wordsof the constitution and the declaration of independence, theywere signing a promissory note to which every american was tofall heir. this note was a promise that all men would beguarranteed the inalienable rights of life, liberty, and thepursuit of happiness.it is obvious today that america has defaulted on thispromissory note insofar as her citizens of color are concerned.instead of honoring this sacred obligation, america has giventhe negro people a bad check which has come back markedinsufficient funds.justice is bankrupt. we refuse to believe that there areinsufficient funds in the great vaults of opportunity of thisnation. so we have come to cash this check -- a check that willgive us upon demand the riches of freedom and the security ofjustice. we have also come to this hallowed spot to remindamerica of the fierce urgency of now. this is no time to engagein the luxury of cooling off or to take the tranquilizing drugof gradualism. now is the time to rise from the dark anddesolate valley of segregation to the sunlit path of racialjustice. now is the time to open the doors of opportunity to allof god's children. now is the time to lift our nation from thequicksands of racial injustice to the solid rock ofbrotherhood.it would be fatal for the nation to overlook the urgency of themoment and to underestimate the determination of the negro. thissweltering summer of the negro's legitimate discontent will notpass until there is an invigorating autumn of freedom andequality. nineteen sixty-three is not an end, but a beginning.those who hope that the negro needed to blow off steam and willnow be content will have a rude awakening if the nation returnsto business as usual. there will be neither rest nor tranquilityin america until the negro is granted his citizenship rights.the whirlwinds of revolt will continue to shake the foundationsof our nation until the bright day of justice emerges.but there is something that i must say to my people who stand onthe warm threshold which leads into the palace of justice. inthe process of gaining our rightful place we must not be guiltyof wrongful deeds. let us not seek to satisfy our thirst forfreedom by drinking from the cup of bitterness and hatred.we must forever conduct our struggle on the high plane ofdignity and discipline. we must not allow our creative protestto degenerate into physical violence. again and again we mustrise to the majestic heights of meeting physical force with soulforce. the marvelous new militancy which has engulfed the negrocommunity must not lead us to distrust of all white people, formany of our white brothers, as evidenced by their presence heretoday, have come to realize that their destiny is tied up withour destiny and their freedom is inextricably bound to ourfreedom. we cannot walk alone.and as we walk, we must make the pledge that we shall marchahead. we cannot turn back. there are those who are asking thedevotees of civil rights, "when will you be satisfied?" we cannever be satisfied as long as our bodies, heavy with the fatigueof travel, cannot gain lodging in the motels of the highways andthe hotels of the cities. we cannot be satisfied as long as thenegro's basic mobility is from a smaller ghetto to a larger one.we can never be satisfied as long as a negro in mississippicannot vote and a negro in new york believes he has nothing forwhich to vote. no, no, we are not satisfied, and we will not besatisfied until justice rolls down like waters and righteousnesslike a mighty stream.i am not unmindful that some of you have come here out of greattrials and tribulations. some of you have come fresh from narrowcells. some of you have come from areas where your quest forfreedom left you battered by the storms of persecution andstaggered by the winds of police brutality. you have been theveterans of creative suffering. continue to work with the faiththat unearned suffering is redemptive.go back to mississippi, go back to alabama, go back to georgia,go back to louisiana, go back to the slums and ghettos of ournorthern cities, knowing that somehow this situation can andwill be changed. let us not wallow in the valley of despair.i say to you today, my friends, that in spite of thedifficulties and frustrations of the moment, i still have adream. it is a dream deeply rooted in the american dream.i have a dream that one day this nation will rise up and liveout the true meaning of its creed: "we hold these truths to beself-evident: that all men are created equal."i have a dream that one day on the red hills of georgia the sonsof former slaves and the sons of former slaveowners will be ableto sit down together at a table of brotherhood.i have a dream that one day even the state of mississippi, adesert state, sweltering with the heat of injustice andoppression, will be transformed into an oasis of freedom andjustice.i have a dream that my four children will one day live in anation where they will not be judged by the color of their skinbut by the content of their character.i have a dream today.i have a dream that one day the state of alabama, whosegovernor's lips are presently dripping with the words ofinterposition and nullification, will be transformed into asituation where little black boys and black girls will be ableto join hands with little white boys and white girls and walktogether as sisters and brothers.i have a dream today.i have a dream that one day every valley shall be exalted, everyhill and mountain shall be made low, the rough places will bemade plain, and the crooked places will be made straight, andthe glory of the lord shall be revealed, and all flesh shall seeit together.this is our hope. this is the faith with which i return to thesouth. with this faith we will be able to hew out of themountain of despair a stone of hope. with this faith we will beable to transform the jangling discords of our nation into abeautiful symphony of brotherhood. with this faith we will beable to work together, to pray together, to struggle together,to go to jail together, to stand up for freedom together,knowing that we will be free one day.this will be the day when all of god's children will be able tosing with a new meaning, "my country, 'tis of thee, sweet landof liberty, of thee i sing. land where my fathers died, land ofthe pilgrim's pride, from every mountainside, let freedom ring."and if america is to be a great nation this must become true. solet freedom ring from the prodigious hilltops of new hampshire.let freedom ring from the mighty mountains of new york. letfreedom ring from the heightening alleghenies of pennsylvania!let freedom ring from the snowcapped rockies of colorado!let freedom ring from the curvaceous peaks of california!but not only that; let freedom ring from stone mountain ofgeorgia!let freedom ring from lookout mountain of tennessee!let freedom ring from every hill and every molehill ofmississippi. from every mountainside, let freedom ring.when we let freedom ring, whem we let it ring from every villageand every hamlet, from every state and every city, we will beable to speed up that day when all of god's children, black menand white men, jews and gentiles, protestants and catholics,will be able to join hands and sing in the words of the oldnegro spiritual, "free at last! free at last! thank godalmighty, we are free at last!"

    <p>&nbsp;</p> <blockquote> <p>I'm not sure what it is, but there continues to be some sort of "competition" for "who can find the biggest bug" -- as if attackers had to choose, and more importantly, as if any bug was so big that it could not be made even better by combined use with its "competition".&nbsp; Before my DNS talk, my old friend FX from Recurity Labs was <a href="http://www.phenoelit.net/lablog/paradigms/Perception_of_Vulnerabilities.sl">comparing DNS issues</a> to the Debian Non-Random Number Generator issue that caused all sorts of SSL certificates to offer no security value, and the SNMPv3 flaws that allowed infrastructure devices to be remotely administered by people who happened not to know the password.  <p>Of course, after the talk, it became clear that the DNS hack and the Debian NRNG combined rather destructively -- DNS allowed you to finally play MITM with all the SSL private keys you could trivially compute, and as Ben Laurie found, <a href="http://seclists.org/fulldisclosure/2008/Aug/0123.html">this included the keys for Sun's OpenID authentication provider</a>.&nbsp; And, since the DNS hack turns Java back into a universal UDP and TCP gateway, we end up being able to log into SNMPv3 devices that would otherwise be protected behind firewalls.  <p>So there's no sense making a competition out of it.&nbsp; There's just an ever growing toolchest, growing from a single emerging theme:  <p><strong><b>Weaknesses in authentication and encryption, some which have been known to at least some degree for quite some time and many of which are sourced in the core design of the system, continue to pose a threat to the Internet infrastructure at large, both by corrupting routing, and making those corrupted routes problematic.</b></strong>  <p>Back in July, the genuinely brilliant Halvar Flake <a href="http://addxorrol.blogspot.com/2008/07/all-this-dns.html">posted the following</a> regarding the entire DNS issue:  <p><em><i>"I fail to understand the seriousness with which this bug is handled though. Anybody who uses the Internet has to assume that his gateway is owned."</i></em>  <p>And thus, why 75% of my Black Hat talk was on the real-world effectiveness of Man-In-The-Middle attacks: Most people aren't as smart as Halvar.&nbsp; I'm certainly not :)&nbsp; Almost <em><i>nobody </i></em>assumes that their gateway is owned -- and even those that do, and try to engineer around it, deploy ineffective protections that are only "secure unless there's an attacker".  <p>I say this is a theme, because it is the unifying element between some of the year's most high profile flaws.&nbsp; There are two subclasses -- some involve weak authentication migrating traffic from one location to another, while others involve weak authentication allowing an attacker to read or modify traffic migrated to him -- but you'd have to have some pretty serious blinders to not see the unifying theme of <strong><b>weak authentication leads to pwnage.</b></strong>  <p>Consider:  <p><strong><b>Luciano Bello's Debian NRNG: </b></strong>This involves a core design requiring the generation of random numbers, but the random number generator required a random seed, but alas, the seed was made insufficiently random.&nbsp; It's an implementation flaw, but barely -- and the effect was catastrophic failure against members of the X.509 PKI authentication system that had used the Debian NRNG, and thus by extension SSL's encryption logic and OpenID (for Sun's) authentication gateway.  <p><strong><b>Wes Hardakar's SNMPv3 Bug: </b></strong>Here, we have an authentication protocol that allows an attacker to declare how many bytes he wants to have to correctly provide.&nbsp; Now, the attacker can claim "just 1 please" -- and he gets into any router suffering this bug within seconds.&nbsp; That, by extension, allows control over all traffic traversing that router.  <p><strong><b>Mike Zusman's Insecure SSL-VPN's: </b></strong>SSL is supposed to protect us, but there's no sense creating a secure session to someone if you don't actually know who they are.&nbsp; Don't worry though, by design anything that <em><i>isn't</i></em> a web browser is terrifyingly likely to only to skip authentication entirely and just create an encrypted link to whoever's responding.&nbsp; One would think that SSL-VPN's, whose sole purpose is to prevent attackers from accessing network traffic, would be immune.&nbsp; But with 42% of certificates on the Internet being self-signed, and a lot of them being for SSL-VPN's, one would be wrong.&nbsp; By extension this auth failure exposes all traffic routed over these SSL-VPN's.  <p><strong><b>Mike Perry's Insecure Cookies:</b></strong> This gets interesting.&nbsp; Here we have two different authentication protocols in place -- one, from server to client, based on X.509.&nbsp; The other, from client to server, based on a plaintext password (delivered, at least, over an encrypted session authenticated by the server-to-client cert).&nbsp; But to prevent the user from needing to repeatedly type in their plaintext password, a password-equivalent token (or cookie) is handed to the user's browser, which will be attached to every request within the securely encrypted channel.&nbsp; Unfortunately, it'll also be attached to every request which does <em><i>not</i></em> traverse the securely encrypted channel, because the cookies aren't marked for secure-only.&nbsp; Once the cookie leaks, of course, it'll authenticate a bad guy who creates an encrypted session to that server.&nbsp; So by extension bad guys get to play in any number of interesting sites.  <p><strong><b>My DNS flaw:</b></strong> Here we have a protocol that directly controls routing decisions, ultimately designed to authenticate its messages via a random number between 0 and 65535.&nbsp; Guess the number, and change routing.&nbsp; This was <em><i>supposed</i></em> to be OK, because you could only guess a certain number of times per day.&nbsp; There was even an RFC entirely based around this time limit.&nbsp; It turns out there's a good dozen ways around that limit, allowing anonymous and even almost 100% packet spoofed compromise of routing decisions.&nbsp; This, by extension, allowed exploitation of all traffic that was weakly authenticating.  <p>It's the same story, again and again.&nbsp; And now, everyone talking about BGP.&nbsp; So lets do the same sort of analysis on BGP:  <p><strong><b>Kapela and Pilosov's BGP flaw:</b></strong> In BGP, only the nearest neighbor is authenticated.&nbsp; The concept is that all "members of the club" authenticate all other members, while the actual data they provide and distribute is trusted.&nbsp; If it's not actually trusted, anyone can hijack traffic from anyone else's routes.  <p>Pilosov's done some cool work here.&nbsp; It's not the sort of devastating surprise some people seem to want it to be.&nbsp; Indeed, that's what makes it so interesting.&nbsp; BGP was <em><i>actually supposed to be broken, in this precise manner.</i></em> Literally, in every day use, any BGP administrator has always had the ability to hijack anyone else's traffic.&nbsp; Pilosov has a new, even beautiful MITM attack, but as mine was not the first DNS attack, his is not the first BGP MITM.&nbsp; Tales of using BGP to force traffic through a compromised router (possibly compromised through SNMPv3) are legion, and Javascript and the browser DOM blur things pretty fiercely in terms of the relevance of being able to pass through to the legitimate endpoint anyway.  <p>That's not to take away from the work.&nbsp; It's an interesting trick.&nbsp; But we need to level set here:  <p>First, if you're not part of the BGP club, you're just not running this attack.&nbsp; Pakistan took out YouTube with BGP -- but some random kid with the ability to spoof IP packets couldn't.&nbsp; In other words, we're just not going to see a Metasploit module anyone can run to complete these sorts of attacks.&nbsp; Now, there are some entertaining combinatorics that could be played -- DNS to enable Java's SNMPv3 access to internal routers at an ISP, and then from that internal router running the sort of BGP tricks Pilosov's talking about.&nbsp; This goes back to the utter folly of trying to rank these bugs independently from one another.&nbsp; But these sort of combinatorics are at a fundamentally different level than the fire-and-forget antics that DNS allowed, and on a fundamental level, the number of potential attackers (and the number of involved defenders) on BGP is a lot lower.  <p>Second, we have far better logging -- and thus accountability -- in the BGP realm than we do perhaps for any other protocol on the Internet.&nbsp; Consider the archives <a href="http://thyme.apnic.net/">at APNIC</a> -- yes, that's route history going back to <em><i>1999</i></em> -- and <a href="http://www.renesys.com/">Renesys</a> has even more.&nbsp; That sort of forensic data is unimaginable for anything else, least of all DNS.&nbsp; BGP may have its fair share of bad actors -- consider spammers who advertise temporary ranges in unused space for mail delivery purposes, thus getting around blackholes -- but any of the really nasty stuff leaves a paper trail unmatched by any other attack.  <p>Third, BGP is something of a sledgehammer.&nbsp; Yes, you're grabbing traffic -- but your control over exactly what traffic you grab is fairly limited.&nbsp; Contrast that with DNS, which allows astonishingly fine grained targeting over exactly what you grab -- indeed, you don't even need to know in advance what traffic you want.&nbsp; The victim network will simply offer you interesting names, and you get to choose on the fly which ones you'll take.&nbsp; These names may even be internal names, offering the impossible-with-BGP attack of hijacking traffic between two hosts on the exact same network segment.  <p>Finally, BGP suffers some limitations in visibility.&nbsp; Simply grabbing traffic is nice, but bidirectional flows are better than unidirectional flows, and when you pull something off via DNS, you're pretty much guaranteed to grab all the traffic from that TCP session even if you stop any further poisoning attempts.&nbsp; Contrast that with BGP, which operates at Layer 3 and thus may cause the IP packets to reroute at any point when the TCP socket is still active.  <p>So, does that mean its always better to attack DNS than BGP?&nbsp; Oh, you competitive people would like things to be so simple, wouldn't you <img border="0" alt=":)" src="cid:image001.gif@01C908F8.E8E197E0" width="15" height="15">Pilosov and I talked for about a half hour at Defcon, and I've got nothing but respect for his work.&nbsp; Lets look at the other side of things for a moment.&nbsp;&nbsp; First, BGP controls how you route to your name server -- if not your recursive server, which may be inside your organization and thus immune to ext</p></blockquote>
    

    钥匙,
    被遗忘在19楼;
    慢慢的消沉,
    有限的生命.
    80楼的门,
    还要多久才能开启.
    太多的幻像,
    被遗忘的钥匙;
    还在爬的楼梯,
    什么时间可以拾起,
    那被遗忘的钥匙.
    重正,
    那昔日的雄风;
    蔚蓝的天空,
    在等待着我们.

    李国栋床上堆着书,每天晚上睡在榻榻米上,读书读到凌晨一两点,读到两眼充血,像针扎一样痛苦,才把书放开。蜷缩到榻榻米上,用绳子把左腿跟一只桌脚绑在一起,熄了灯睡觉。
        “这样一来,我一翻身,扯不动腿,就会醒过来。醒过来就马上爬起来继续看书——今年是第三年了,再考不上,就要当兵去了!”
        高考前,李国栋很平静地这样解释他的生活方式。他消瘦的脸颊上浮着一层暗暗的青气,眼白里满是一条条细细的血丝。讲话的时候,眼神涣散,不知道他在看哪里。
        “为什么不换个读书方法?这种煎熬式读书不是效果很差吗?”
        他摇摇头:“我不知道还有什么别的方法。”
        “为什么不找其他出路?不上大学,去读职校或学技术?”
        他开始咬指甲,每一片指甲都咬得烂烂毛毛的:“不行,我非读大学不可。”
        李国栋后来仍旧落了榜,但是也没去当兵。他在精神病院里住了两个星期之后,有个晚上,偷偷吞了五枚大铁钉,从七楼的阳台上跳下来,刚好掉在垃圾车旁边。
        麦尔教授对老鼠很有兴趣,曾经做过这样的实验。
        他把老鼠聚集在一个平台上,让它们一个个往下面两个门上跳。跳向左门,它会碰得鼻青脸肿;跳向右门,门就会打开,门后是甜美的乳酪。小老鼠当然不笨,训练几次后,就快快乐乐地往右门跳去,不再摔得一鼻子灰。
        可是,就在小老鼠的选择方式固定了的时候,麦尔把乳酪从右门移到左门。本来以为可以饱食一顿的老鼠现在又碰得鼻青脸肿,它不知道客观情势已经改变了。幸好,摔了几次后,它又渐渐熟悉了新的情况,原来乳酪在左边!
        问题是,麦尔又有了新花样。他把门的颜色重新漆过,把乳酪一会儿放左,一会儿放右。老鼠在新的习惯形成之后,发觉原来的方式又行不通了,它必须不断地适应新情况,不断地修正自己的习惯行为……
        终于,老鼠变不过来了,它的下一个反应就是“以不变应万变”。麦尔发觉,在应变不过来的时候,老鼠“拧”了,开始固执起来,根本就拒绝改变方式。譬如说,如果它已经习惯于跳向左门,你就是把乳酪明明白白地放在右门上,让它看见,它仍旧狠狠地往左门去碰肿鼻子,愈碰就愈紧张。如果实验者在这个关口继续强迫它去作跳左或跳右的抉择,老鼠就往往会抽筋、狂奔、东撞西跌或咬伤自己,然后全身颤抖直到昏迷为止。换句话说,这只老鼠已经“精神崩溃”了。
        于是,麦尔教授归纳出导致老鼠“精神崩溃”的五个阶段:
        首先,对某一个难题(左门或右门),让老鼠逐渐培养出一种应对的习惯来(选择右门:右门有乳酪)。
        第二个阶段,客观环境改变,老鼠发觉惯有的方式已经不能解决问题,因此感到恐惧。
        第三个阶段,不断的焦虑与挫折、失败之后,它就固执地以旧有的方式面对新的情况,不计后果(就是看见乳酪出现在右边,仍旧往左边闯)。
        第四个阶段,根本放弃努力(乳酪也不吃了,干脆饿死)。
        最后,如果外力迫使它非解决问题不可,它就又回到它所习惯的旧方式(左门就是左门,非左门不可)。当然又碰得鼻青脸肿,饿得头昏眼花。明明只要换个途径就可解决一切,它却固执地在习惯行为中饱受挫折与失败的煎熬,最后以崩溃结束。
        在垃圾车边被清洁工人发现的李国栋是一只弄“拧”了的老鼠,我们的社会环境与教育制度是控制乳酪、制造难题的科学家。从前,大学之门是通往乳酪的门,所有的人都往那个门上跳。“士大夫”观念深深地植入人们心中,因为我们发觉成了“士大夫”之后就有甜美的乳酪可吃。但是,在大家都习惯了这个方式之后,客观情况却变了,乳酪换了门。往“士大夫”那个门撞去,就会撞个鼻青脸肿,而且得不到乳酪。
        可是孩子们继续去撞那一扇门。做父母的也继续鼓励孩子们去撞那扇没有乳酪的门。他们说“有志者,事竟成”;说“精诚所至,金石为开”;说“老天不负苦心人”。门的颜色变了,乳酪的位置换了,可是弄“拧”了的人固执地守着旧有的方式“以不变应万变”。
        一个人,也只不过是只有可能精神崩溃的老鼠。人生的每个阶段里都有看似不可解的难题时时强迫他作出抉择:考试失败了、爱人变心了、婚姻破裂了、工作失去了,每一个难题都需要一个解决的办法。究竟乳酪在左边还是右边?不管左右,当一个人不再能以“新”的方式来应付“新”的情况,当他不计后果,根本拒绝改变自己的时候,他就是一只弄“拧”了的老鼠,精神的解体只是自然的结局。如何能不受制于旧习惯、旧观念、旧方法,如何不因搞“拧”了老去撞一扇没有乳酪的门,需要的是弹性与智慧。
        智慧,不正是人之所以为人,鼠之所以为鼠的差别吗?

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

    |=---------------------------------------------------------------------------=|
    |=----------------------=[       安全幻想曲2008       ]=---------------------=|
    |=---------------------------------------------------------------------------=|
    |=---------------------------------------------------------------------------=|
    |=--------------------=[           By axis             ]=--------------------=|
    |=--------------------=[   <axis_at_ph4nt0m_dot_org>   ]=--------------------=|
    |=---------------------------------------------------------------------------=|

        我见过的大多数安全人员,都对技术有着一种狂热,甚至是一种偏执。这种情绪在做安
    全研究员的时候是非常有好处的,因为作为研究员,可能要偏执考虑到一些极端的情况。这
    种钻研精神,是光靠勤奋所无法达到的。但是在甲方做安全的话,可能更多时候需要的就不
    是狂热,而是掌握平衡的艺术。在商业利益与安全性发生冲突时,如何处理好这个平衡,是一
    个关键。

        举一个简单的例子来说,眼下最流行的XSS攻击,其修补方案从总体上来说,大致可以分
    为escape output和filter input两种。对于狂热的安全人员来说,当然是恨不得把网站全
    部弄成静态的,输出都采用escape output,全部输出纯文本,就天下太平了。然而现实与理
    想总是有差别的,首道难关就是网站肯定会有些富文本的需求。

        当安全和需求相抵触时,一定是安全给商业需求让路。这里要避免一个误区,就是安全
    应该是为需求而服务的,而不是成为需求的障碍。其实这个观点大多数人都心知肚明,但是
    在实际操作起来的时候往往会事与愿违。

        再回到富文本上来,当需求决定需要有富文本输出的时候,狂热的安全人员(下称为狂战
    士吧)就只好退而求其次,要求对富文本做filter input,对其他没有富文本的地方做escape
    output。接下来问题来了,对于程序员来说,富文本往往采用了一些第三方的,或者是基于第
    三方的富文本编辑器,还有的是自己实现了一个。而这些富文本编辑器,往往在考虑
    xss defense的时候有所欠缺。这时候采用什么样的策略来做filter input,就成为了新的
    问题。

        第一个难关就是程序员会拉上商业,一起来和狂战士PK,说filter input很容易误杀客
    户的正常操作,还会影响到性能。当然这小小的难关还难不倒狂战士。狂战士往往会轻蔑的
    一笑,然后把风险推到商业上,说出了问题让他背黑锅之类。这种狠话一放出来,商业往往就
    会退缩了,毕竟狂战士这么个狠角色是摆在那里的。所以最后会决定让程序员去整filter。

        于是程序员简单写了个基于正则的blacklist,并且禁用了部分标签,比如script。狂战
    士这时候又蹦了出来,对程序员指手画脚,要求禁用style,因为这玩意太难控制了,黑客有几
    百种利用style的方式;狂战士还说,基于正则的匹配这个魔法等级太低了,要换个高级魔法,
    比如个语法分析器,类似html purify这种,还要有log analysis和realtime monitor功能。

        一般到了这个时候,程序员对狂战士的忍耐已经差不多到极限了,因为甲方网站很少以
    安全为主要考核因素,没人会认为一个视频网站或者是交友网站的安全需要做的比FBI更好,
    因为没那么大的成本投入。于是程序员说要释放这么个高级魔法需要一个团的程序员配合,
    还需要召唤很长时间才能放出来,所以狂战士的这个非常牛B的魔法无法完成。而一般在这
    个时候,程序员往往会用啥性能和稳定性之类的因素来忽悠狂战士,说这种魔法一般有一定
    概率会反噬,没整好就把自己整残了。

        狂战士无奈之下,只好同意程序员实现一部分的魔法,filter部分过滤完整有效就行了。
    做好这个之后,狂战士还让程序员去对没有富文本需求的地方使用escape output。程序员
    这时候对狂战士已经忍无可忍了,因为由于以前从来没有注意过xss这方面的问题,所以需要
    escape的地方是以“千”或者是“万”为单位的,多如牛毛。于是程序员开始消极怠工,并且开
    始诉苦。这条路走不通了,狂战士只好开始寻求更好的方案。

        后来狂战士回家睡了一觉,在梦中有仙人传授武艺,于是马上想到了新的办法。第一招
    是filter output,不过这个扯淡的方法根本属于yy,因为对服务器压力太大。第二招是使用
    WAF,就是web application firewall,开个虚拟补丁,这样程序员不补也能搞定web漏洞。不
    过这样就依赖于WAF的规则了,而且治标不治本。看来昨晚那个仙人估计是灶君一类低级的
    小神,尽出馊点子。看来狂战士还得继续和程序员PK下去了。

        可以见到,那些牛圈里的狂战士常认为是“奇技淫巧”的XSS问题里,有这么多头疼的问题。
    简单的问题变得越来越复杂。

        安全是一个持续的过程(process)。既然是过程,就会有第一步、第二步 ... 第N步,有
    一个持续的概念在里面,不能今天整了,明天就不管了。今天的安全并不代表明天的安全,新
    的技术和应用在不断发展,就会不断带来新的问题。经常看到一个升级反而把漏洞升级出来
    的例子。所以安全是一个持续的对抗过程,hacking与anti-hacking的过程,广义来说,更是
    一个弱化风险的过程。

        很多BOSS往往都会这么问狂战士:我上了这个720安全卫士是不是桌面安全就不用管了?
    我上了这个卖红茶IPS是不是就能挡住所有刺客入侵了? 狂战士这时候很无奈的说:不行,还
    是有很多trojan和rootkit可以bypass主动防御,很多shellcode和0day可以anti IPS。 于
    是BOSS很生气的说: 那我花这么多钱买这个做啥? 狂战士一般会忽悠他说:上了这个可以解
    决90%的攻击。 于是BOSS会很不满意,让狂战士出技术分析报告,一定要有充分的理由才行,
    狂战士往往要面对这种烦恼。

        其实BOSS的这种观点是一种急功近利的想法,没有认识到安全是一个过程,并且是一个
    持续改进的过程。不是买个box就能解决问题的。没有100%的安全,有漏洞的地方太多了。
    经常有魔法师用木桶原理来阐述安全问题,但其实很多时候,连木板在哪里,到底那块木板才
    是短板,都没有一个很清晰的认识,因为很多时候根本无法量化,所以狂战士的工作经常陷入
    误区。板子太多了,系统、网络、用户、应用、数据、桌面......

        放眼看去,全是短板,每块板子都能让刺客或盗贼轻松的进来,偷走核心数据或者弄摊网
    站然后扬长而去。或者各种短板互相组合,让问题变得更加扑朔迷离。

        前面说的WAF就是一种比较功利的做法,虽然厂商经常会蹦出来说这玩意是需要有专人
    维护的,也是一个持续的过程。但实际上很多购买WAF的用户都没有好好的去做这个过程。
    其实WAF、IPS最大的软肋不是在没人跟进上,而是在于其是串联的网络上的,特别是开了虚
    拟补丁的阻断模式的时候。这对于高可用性的应用来说,绝对是无法忍受的。没人敢背这个
    误杀的黑锅。要是因此导致了PV下降,可能老板就要喊到办公室去喝茶了。不过WAF也不是
    完全没用,如果能够用好的话,对于网站还是还是很有帮助的,至少在monitor和攻击流量分
    析上起着积极的意义。不过前提是用好。

        刚才说了安全是一个过程(Process),其实有人跟进这个过程还不够,下面还要重点说说
    深度防御的思想。经常看到YY小说的作者在写到黑客攻防的时候,说到XXX在xx分钟内就突
    破了N道防火墙,N大于100;变形金刚里也这么有这种场景。其实这纯粹是扯淡,没事整那么
    多防火墙做什么,无端影响了可用性。不过YY作者深度防御的理念还是正确的,只是他不知
    道那玩意不应该单纯叫防火墙,要想表达这个思想,可以整个专业名词,比如:多层防御体系。
    这样装B就可以装的比较像样了。举例来说,可以在应用层校验用户输入数据,DB层面检查每
    条sql,操作系统上细分权限,服务最少化,网络上防御arp spoof,加密传输通道,做好ACL…类
    似措施还有很多,防御的方案交叉层叠起来,就能起来一个比较好的保护效果。

        不过偏偏还有不识趣的,比如前面的很多程序员都会说,我都已经做了filter input,还
    要escape output做啥。狂战士一般听到后会有想要狂化的冲动。按耐住狂化,告诉程序员,
    说filter input可能会做不干净,会被bypass,毕竟如果遇到一个手执绝世0day(bypass
    filter)的9级刺客,什么牛B的防御魔法都挡不住,所以能escape output的地方,最好escape
    掉,这样最干净。可是即便是这样做好了,还是有些会有很难处理和发现的地方,比如在DOM
    里的XSS,比如在JS里面一些写的很BT的地方,等。这些只能靠肉眼去看了。PK还得进行下去。

        但是程序员还是不能很好的理解,他们跑出来说:我这里做了完善的access control,只
    有管理员才看的到,这里就算有注射有跨站就随他去了,不需要修复。想偷这种懒的人其实
    不在少数。这种想法违背了深度防御的思想。先姑且不论如果管理员密码泄露,或者管理员
    是个内鬼的情况。如果刺客通过注射拿到了管理员密码,或者是直接通过XSS和CSRF来对后
    台进行注射,那么前面的access control就完全没作用了。

        在一定程度上,是可以容忍风险的存在的,但是从长期来看,这种做法是非常不可取的。
    比如有的管理员会说防火墙只允许80端口,那么RPC漏洞或开其他端口的应用漏洞是否就可
    以不补了。也许一时来说是没什么问题,但是如果放置不管将导致没有人来维护漏洞,也许
    哪天的防火墙策略变更,或者来自内部系统的威胁,都有可能导致当时看起来无害的漏洞被
    利用。而这种做法的一个后果往往是难以检查原因,就是说咋死的都不知道。所以这又回到
    了开始的话题:安全是一个持续的过程。

        在灌输完深度防御的思想给程序员以后,狂战士又被另外一种程序员打击到崩溃了。面
    对满目都是红色的扫描报告,他们说:我这个xxx ftp没漏洞,除非狂战士可以证明黑客能搞
    进来拿到shell。一般狂战士听到这种要求,狂化的概率在80%以上。首先,不是只有能拿到
    shell的才叫漏洞。一个dos可能会造成业务的中断,一个infomation leak可能会为后续攻
    击带来便利,等等。

        面对scan report以及CVE查询出来的漏洞,大部分都是没有现成的exp能够利用的,而且
    要利用漏洞可能有各种苛刻的条件,比如要求本地交互shell啊,或者要求有帐户之类。而更
    多的时候,漏洞根本连细节的都没有,只有一个漏洞公告里一个简单的划分critical,标红。
    就算有exp,可能还要考虑到exp的稳定性和成功率、语言版本啥的,打过去也不一定能成功。
    更何况狂战士无法处心积虑了为了POC给程序员看,而花费大量的精力来追求一个可能没有
    结果的漏洞。

        但是无法POC不代表就没有风险了。我们的目标是要保证一个系统长期的在任何情况下
    都能安全运行,机密数据不会外泄,业务不会中断。所以这种程序员犯的错误就是偷换了概
    念,把威胁范围缩小了,用个体来代替全局。很多时候威胁可能来自内部,可能来自误操作,
    可能来自其他的风险。要说服这种程序员很辛苦,只能够靠长期的“忽悠”,来慢慢感化他们,
    要是运气好还能做出一两个POC来震撼下他们,刘震撼(ZhenHan.Liu)就是为此而生的。佛曰:
    我不下地狱谁下地狱。

        作为一个优秀的狂战士,往往要有相当程度的mission impossible的修为。很多时候,
    需要为浏览器漏洞、操作系统漏洞擦屁股,不然最后吃亏的还是自己的用户。面对钓鱼和诈
    骗,很多时候那些认为web安全是“奇技淫巧”狂战士们认为解决方案是impossible的,认为
    no patch for stupid。比如phishing,诚然,如果有一个一劳永逸的方案,那么这种完美魔
    法要是放出来了绝对可以获得圣阶魔导师的称号。但是YY归YY,现实归现实。狂战士们很头
    疼这种问题,但是却不得不去面对它。

        魔法最终还是放出来了,可惜不完美。目前anti-phishing的魔法,有整到浏览器里内置
    对抗的(IE7/8),也有浏览器toolbar、扩展的,有在IM里做过滤的,还有穷举malicious sites
    的,更有发动人民战争来维护一个blacklist的,其难度和成本从低到高什么都有,不过基本
    都无法一次性解决问题。比较有创意的魔法属于yahoo发明的sign seal,基于认证机器的原
    理来识别真实网站,不过这个方法的缺陷在于需要长期教育用户,实际使用效果不一定好。
    yahoo还整了个domainkey技术来在邮件里对抗phishing,不过这个缺陷更明显,需要邮件服
    务商支持。yahoo的狂战士挺有想法的,就是太理想化了一点。

        说到安全世界的另外一股强大力量不能不提教廷,这个宗教从精神上统治了安全世界,
    一群群红衣主教们整出来了一堆标准、规范比如BS7799之类来帮助狂战士们更好的忽悠他
    们的BOSS。其实标准是死的,主教们的出发点是好的,不过这些标准啥的就和秘籍差不多,狂
    战士们以为他们读明白了,其实很少人真正读懂了。那玩意如果拿来忽悠BOSS们确实是一套
    套的,但用在实处则有一个本地化的过程。必须要把标准之类的东西和实际情况结合起来,
    不然就只能停留在忽悠的层面上。

        最能体现问题的出在编码规范上。可能有N个权威的机构都出了他们自己的code规范,
    或者某些狂战士佣兵团(安全公司)也自己整了套。不过在具体使用的时候,很多狂战士都是
    拿了一套去用在所有的公司身上,其实这样的结果就是到最后没有程序员遵守用那玩意,因
    为在实际情况中往往不好用。每个公司都有自己的体系、环境和编码习惯。系统的designer
    和architect只要不是小白一般都或多或少的会考虑点安全风险,规范只有本地化以后才能
    很好的用起来,不然绝对会水土不服。所以要是再遇到什么安全公司拿标准、规范来忽悠的
    时候,狂战士们就要睁亮了眼睛了!

        胡侃瞎吹了这么多其实也没说到重点,不过重点已经不是本文要讲的事情了,想要讲的
    东西还有很多,也许以后会陆续写出来。狂战士是份很好的职业,希望有更多的狂战士甚至
    是半兽人朋友能够加入我所在的狂战士佣兵团!

    -EOF-