写游戏修改器的流水账
分类:技术
昨天很郁闷的在玩仙剑五打七圣的时候被太武老爷子调戏了三四个小时,于是玩游戏一贯很讨厌用作弊器的俺打起了作弊的念头。因为懒得找专用修改器和金山游侠的下载,作为一个死程,自然是要自己动手写代码咯~
基本原理非常之简单:既然程序运行时所需数据都保存在内存中,那么如果要修改游戏里的某项数值的话,直接去保存数值的地方修改就好了。
首要问题在于:茫茫内存,我他喵的怎么知道 HP 存哪儿去了?
分类:技术
昨天很郁闷的在玩仙剑五打七圣的时候被太武老爷子调戏了三四个小时,于是玩游戏一贯很讨厌用作弊器的俺打起了作弊的念头。因为懒得找专用修改器和金山游侠的下载,作为一个死程,自然是要自己动手写代码咯~
基本原理非常之简单:既然程序运行时所需数据都保存在内存中,那么如果要修改游戏里的某项数值的话,直接去保存数值的地方修改就好了。
首要问题在于:茫茫内存,我他喵的怎么知道 HP 存哪儿去了?
嗯哼~坑爹的保护成员果然有个坑爹的来历。作为 C++ 之父的 Bjarne Stroustrup 大叔在他的大作 The Design and Evolution of C++ 中写道:(以下引自中文版《C++ 语言的设计和演化》,第 13.9 节)
在 Release 1.0 推出后不久,Mark Linton 顺便到我的办公室来了一下,提出了一个使人印象深刻的请求, 要求提供第三个控制层次,一边能直接支持斯坦福大学正在开发的 Interviews 库中所使用的风格。我们一 起揣摩,创造出单词 protected 以表示类里的一些成员,它们对于这个类和它的派生类“像公用的”,而对其 他地方就“像私用的”。
Mark 是 Interviews 的主要设计师。它的有说服力的争辩是基于实际经验和来自真实代码的实例。他论证 说,保护数据对于设计一个高效的可扩充的 X 窗口工具包是最关键的东西,而可能替代保护数据的其他方式 都因为低效、难以处理在线界面函数或者使用数据公开等等,因而是无法接受的。
…(略)…
大约五年之后,Mark 在 Interviews 里禁止了保护数据成员,因为它们已经变成许多程序错误的根源 …(略)… 实际上,我对 protected 的关心正在于它将导致使用一个基类变得太容易,就像人们可能因为懒惰而使用全局数据一样。
…(略)…
保护成员是 Release 1.2 引进的,保护基类最早是在 ARM 里描述的,Release 1.2 提供了它。回过头看,我认为 protected 是“好的论据”和时尚战胜了我的更好的判断和经验规则,使我接受新特征的一个例子。
话说我能顺便吐槽下这悲催的中文翻译么?最后一段完全不是翻给地球人看的嘛 :P
分类:技术
什么东西一旦追求起效率来最终还是要归到比较底层的操作,比如 GDI 中直接操作位图数据就要用 GetDIBits
/ SetDIBits
(或者已经废弃的 GetBitmapBits
/ SetBitmapBits
)。因为最近要处理的都是 GDI 处理不了的 PNG 格式图片,所以还是用上了 GDI+。GDI+ 中直接操作 Bitmap
的数据就要用 LockBits
/ UnlockBits
了。
第一眼看见 Bitmap::LockBits
的声明我就比较晕:
Status LockBits(const Rect *rect,
UINT flags,
PixelFormat format,
BitmapData *lockedBitmapData
);
不过说实话,整个 GDI+ 库的风格相对于 M$ 的其它库来说已经是很清新脱俗了 :)
rect
NULL
进去表示全图啊~)flags
UINT
类型可取的值其实是 enum
)format
lockedBitmapData
Scan0
会指向被锁定的像素区域(如果 flags
里指定自己分配缓冲区的话,系统只是往 Scan0
所指缓冲区写数据)。那么锁定全图进行读操作就是:
int w = bmp->GetWidth();
int h = bmp->GetHeight();
BitmapData bmpData;
bmp->LockBits(Rect(0, 0, w, h), ImageLockModeRead, PixelFormat32bppARGB, &bmpData);
锁定完图像区域,就可以对得到的 BitmapData
进行操作了。BitmapData
包含了被锁定区域的长、宽、格式、指针信息。取坐标 (x, y) 的像素颜色可以用:
unsigned int *pData = reinterpret_cast<unsigned int *>(bmpData.Scan0);
int stride = bmpData.Stride;
unsigned int color = pData[y * stride / 4 + x]; // color= 0xAARRGGBB
有一个比较特别的 Stride
成员,它表示「一行」图像对应的缓冲区所实际占用的字节数(因为位图文件有一条变态的规则:图片数据在存储时每一行字节数必须是 4 的倍数,如果真实图片数据宽度不是 4 的倍数则需要用垃圾数据补齐不足的字节数,于是就造成了 Stride
≠ Width
的现象,即所谓的字节对齐)。
对于图片数据操作完以后要记得对锁定区域进行解锁:
bmp->UnlockBits(&bmpData);
这样一来就功德圆满了。
嗯~这段笔记就是这样,这真是有意义的一天啊~
咳咳~距刚刚写这篇东西已经有大半年了,在仔细看 GDI+ 的文档时又发现了些有用的东东。
正如你所看到的,上边所说的「LockBits
→ 读写 bmpData.San0
→ UnlockBits
」的三部曲其实并不与 GDI 中的 GetDIBits
/ SetDIBits
完全对应。至少后者是直接从位图中读取数据到我们自己提供的颜色缓冲区、直接从自己的颜色缓冲区写到位图中去,而前者却需要在 Lock 后一行一行、甚至一个像素一个像素地手动交换。
好消息是 Bitmap::LockBits
的 flags
参数除了可以传入 ImageLockModeRead
和 ImageLockModeWrite
外还可以同时或上一个 ImageLockModeUserInputBuf
。这个标志位表示让 LockBits
使用我们传入的 BitmapData
中的缓冲区信息来进行读写,而不是由它来分配、我们来读写。
举个栗子 :)
#define BPP_ARGB 4 // ARGB 每像素所占字节数
int w = bmp->GetWidth();
int h = bmp->GetHeight();
unsigned char *buffer = new unsigned char[w * h * BPP_ARGB]; // 缓冲区
BitmapData bmpData;
bmpData.Width = w;
bmpData.Height = h;
bmpData.Stride = w * 4; // 缓冲区每行大小,自行分配,每行就没有多余字节了
bmpData.Scan0 = buffer;
bmpData.PixelFormat = PixelFormat32bppARGB;
bmpData.Reserved = NULL;
bmp->LockBits(Rect(0, 0, w, h),
ImageLockModeRead | ImageLockModeUserInputBuf,
bmpData.PixelFormat,
&bmpData);
bmp->UnlockBits(&bmpData); // 此时 buffer 中即为 bmp 的颜色数据