SCColoredID 界面

我知道早在几年前就有了这种工具。不过我所找到的这类工具都不大好用,比如 W.Z.Q 的那个,每次修改都要从游戏里切换出来,很不方便。因此一怒之下自己写了个,具备全局快捷键修改,以及 ID 列表保存能力。

同时附上 Delphi 源代码。其实程序很简单,就是用一个循环的 ReadProcessMemory() 来读取 starcraft.exe 进程的内存,并且通过搜索一个特征字符串找到游戏的 ID 列表位置,然后用 WriteProcessMemory() 写入我们设置的彩色 ID 而已。至于如何让 ID 以各种颜色显示,则要感谢前人 W.Z.Q 的研究。事实上我是使用 WinHex 查看经他的 scRedStorm 修改过 starcraft.exe 进程的内存得到的这个“秘密”。

更新历史

v0.1.0221.874

  • 解决对 1.08 以上版本无效的低级问题。

v0.2.0

  • 即时预览彩色 ID,精确模拟星际争霸对待彩色代码的行为。
  • 支持 Windows Vista。为了避免与 Vista 的快捷键冲突,以前的 Ctrl + Alt + Tab 已经更改为 Shift + Tab。
  • ID 首尾都可以使用空格。
  • 放弃 FlatStyle 控件,在 Vista 里它已经不再好看了。

下载

SCColoredID v0.2.0
SCColoredID v0.2.0 源代码

标签:,

虽然早用上了 Delphi 2005 Update 1 正式版,这张光盘已经没有实际意义,但还是挺兴奋的,毕竟是 Borland 寄给我的东西,呵呵。

平心而论 Delphi 2005 是个不错的产品,虽然我们可以找出一堆数落她的理由。比如启动太慢,占用内存太多,IDE 太像 Visual Studio 而失去了 Delphi 传统的风格,编辑器反应比较慢等等。不过在同一个 IDE 里同时支持 Delphi for Win32、Delphi for .NET 以及 C# 确实是个很诱人的卖点。以及新增的 Refactoring、UML Modeling 等等都是很实用很重要的功能。

Delphi 2005 是个不错的产品,但 Borland 对她的宣传确实有些过了。

标签:,

某次看 D6DG 说默认的参数传递方式因为会为变量产生本地副本所以会消耗额外的内存,而 const 方式会优化字符串和记录类型的参数传递时的内存占用。从而猜测 const 方式的参数传递实际也是按地址传递,只是编译器强制不允许函数内的代码修改 const 方式传递进去的变量而已。

为了证实我的猜想,特设计以下实验,本实验中使用到了字符串(本文提到的字符串都是指 Delphi 默认的字符串 AnsiString)内部结构中的引用计数。

关于字符串的引用计数,结合 D6DG 中的说明和偶的实际研究,得出的结论是,字符串地址实际上是其内容的第一个字符的地址,在此地址之前的 12 字节内存中的内容才是字符串内部结构中的头部,分别是 32 位字符串占用的内容空间大小,32 位引用计数,32 位字符串长度(仅在 Delphi 7 中验证,推测 Delphi 7 以前的 32 位 Delphi 中的字符串结构都应该是这样,未验证)。

以下代码中 sGlobal 为全局字符串变量。

procedure Method1(S: string);
var
  P: PInteger;
begin
  // S := S + 'k';
  // 字符串实际上是指针,这里只是将其强制转换为 PInteger 备用
  P := PInteger(S);
  // 得到字符串地址左偏移 8 字节的地址,也就是字符串的 32 位引用计数存储的位置
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('S 引用计数: ' + IntToStr(P^));
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('S 地址: ' + IntToStr(Integer(Pointer(S))));
  Form1.Memo1.Lines.Add('');
end;

procedure Method2(var S: string);
var
  P: PInteger;
begin
  P := PInteger(S);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('S 引用计数: ' + IntToStr(P^));
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('S 地址: ' + IntToStr(Integer(Pointer(S))));
  Form1.Memo1.Lines.Add('');
end;

procedure Method3(const S: string);
var
  P: PInteger;
begin
  P := PInteger(S);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('S 引用计数: ' + IntToStr(P^));
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('S 地址: ' + IntToStr(Integer(Pointer(S))));
  Form1.Memo1.Lines.Add('');
end;

procedure sGlobalInfo;
var
  P: PInteger;
begin
  P := PInteger(sGlobal);
  P := PInteger(Integer(P) - 8);
  Form1.Memo1.Lines.Add('sGlobal 引用计数: ' + IntToStr(P^));
  Form1.Memo1.Lines.Add('sGlobal 地址: ' + IntToStr(Integer(sGlobal)));
  Form1.Memo1.Lines.Add('');
end;

在主程序里分别以初始化后的 sGlobal 为实参调用这几个过程后,我们会看到三种方式 S 的地址都跟 sGlobal 的一样,这初步说明 var 方式和 const 方式确实都是按地址传递参数。

但不可思议的是,默认参数传递方式不会是为变量产生本地副本吗?为什么 S 的地址还会跟 sGlobal 一样呢?因为一开始我设计这个实验时并没有添加显示引用计数的代码,所以很是迷茫。之后想起了 Borland 使用了引用计数的方式和 copy-on-write 技术(对于字符串等使用这两个技术的数据类型的变量 A、B,将 A 赋值给 B 时实际上只是赋值 A 的地址给 B,并增加 A 的引用计数,直到两者其中一个被修改时才为 B 申请新的内存空间并且复制 A 的内容,同时减少 A 的引用计数)来优化字符串的操作。于是查阅 D6DG 并且观察 Delphi 的汇编代码和内存(在 CPU 窗口中)得出了本文开头关于字符串引用计数的结论。

这样,添加了显示引用计数的代码之后我们就可以观察到,使用默认方式时 S 和 sGlobal 的引用计数,都是 2,而其他方式时两者的引用计数都是 1,这更有力的说明了 const 方式确实是按地址传递,而不是像默认方式那样只是增加引用计数。这样,当我们把第一个过程中第一行代码的注释去掉后再次运行程序,可以看到默认方式时 S 的地址已经跟 sGlobal 不同了,同时两者的引用计数都是 1 了,说明确实是在 S 被修改后才产生本地副本(copy-on-write)。

那么,现在我们知道参数类型为字符串时, const 方式的参数传递实际是传递参数地址,这可以优化内存的使用,而参数为其他数据类型时是不是这样呢?

为此修改刚才的程序如下,其中 iGlobal 为全局整形变量。

procedure Method1(I: Integer);
begin
  Form1.Memo1.Lines.Add('I 地址: ' + IntToStr(Integer(@I)));
  Form1.Memo1.Lines.Add('');
end;

procedure Method2(var I: Integer);
begin
  Form1.Memo1.Lines.Add('I 地址: ' + IntToStr(Integer(@I)));
  Form1.Memo1.Lines.Add('');
end;

procedure Method3(const I: Integer);
begin
  Form1.Memo1.Lines.Add('I 地址: ' + IntToStr(Integer(@I)));
  Form1.Memo1.Lines.Add('');
end;

procedure iGlobalInfo;
begin
  Form1.Memo1.Lines.Add('iGlobal 地址: ' + IntToStr(Integer(@iGlobal)));
  Form1.Memo1.Lines.Add('');
end;

在主程序里分别以初始化后的 iGlobal 为实参调用这个过程后,可以看到只有 var 方式中 I 的地址跟 iGlobal 一样,而默认方式和 const 都会为 iGlobal 产生本地副本,可见确实如 D6DG 所说 const 会(也只会)优化字符串和记录类型的参数传递时的内存占用。

至此实验目的达到,还附带了解了 string 的内部格式。

附上实验程序源代码

标签:,