游戏开发论坛

 找回密码
 立即注册
搜索
查看: 8565|回复: 1

为《魔兽III》添加自己的本地函数

[复制链接]

41

主题

305

帖子

413

积分

中级会员

Rank: 3Rank: 3

积分
413
QQ
发表于 2006-8-8 13:37:00 | 显示全部楼层 |阅读模式
相信应该有不少人看见过这个帖子,那么有兴趣的人也许可以自己试试啊,做的好的话,可能会成为一个流行的增强外挂呢(PS,这里的外挂没有贬义,仅是辅助程序的意思)。

感谢白银の游戏王的精彩翻译

提醒:testmap.rar中的loader无需复制到魔兽文件夹即可使用,但是需要确保您的注册表中HKEY_CURRENT_USER\Software\Blizzard Entertainment\Warcraft III\InstallPathX键值指向您魔兽的安装文件夹。
loader.rar中的文件必须复制到魔兽文件夹方可使用,不检查您的注册表。
如果你测试一直失败,也许你需要更换你的免CD补丁。

以下是译文:

   在这篇指南中,我将告诉你如何使用xttoc's jAPI来些你自己的War3本地函数。你必须先了解C/C++并且有一个你了解其调用协定的编译器。我将使用Borland's bcc 5.60。很不幸的,我无法得到任何免费的工具。

  理论上使用这个技术我们可以做任何事,虽然可能需要大量的研究。但有一个严重的限制,那张地图无法在BNet上发布。我要明确的警告某些为地图写运行库的人,因为那将导致安全问题。不管怎么样,我们先开始。在C程序中数据结构特别细致。我们将继承两个:一个堆-只要动态分配数组即可。然后我们做一些更有想象里的事,一个迷宫的不相交集。

  首先先从这个链接下载jAPI Tool。http://www.wc3campaigns.net/showthread.php?t=79652。

  把这些材料Copy到你的War3目录,除了jNatives文件夹外-你需要留下文件夹,但要清空其中的内容。你很可能需要为loader建立快捷方式,只需为LoaderWar3.exe添加标记一个window命令行来测试,如"D:\games\Warcraft III\LoaderWar3.exe" -window

  我们需要做的流程概要:
-写本地函数
-将本地函数导入War3
-将本地函数的声明添加到common.j
-用本地函数写代码
-运行游戏

我们以经典的Hello World开始。以下是test.cpp


  1. /*
  2. The following routines are all exported by japi.dll:

  3. char* MemStrSearch(unsigned char *start, unsigned char *end, char *str);
  4. bool MemPatternCompare(unsigned char *address, unsigned char *pattern, unsigned char *mask, unsigned long length);
  5. unsigned char* MemPatternSearch(unsigned char *start, unsigned char *end, unsigned char *pattern, unsigned char *mask, unsigned long length);

  6. PIMAGE_SECTION_HEADER GetImageSectionHeaders(HMODULE hModule, WORD *count);
  7. PIMAGE_SECTION_HEADER GetImageSectionHeader(HMODULE hModule, unsigned char name[8]);

  8. void        jAPI jBindNative(void *routine, char *name, char *prototype);
  9. void        jAddNative(void *routine, char *name, char *prototype);
  10. jString jAPI jStrMap(char *str);
  11. char*        jAPI jStrGet(jString strid);
  12. */
  13. // - Andy Scott aka xttocs


  14. #include <windows.h>
  15. #include <stdio.h>

  16. #define jNATIVE        __stdcall
  17. #define jAPI        __msfastcall

  18. #define FloatAsInt(f) (*(long*)&f)
  19. #define IntAsFloat(i) (*(float*)&i)

  20. typedef long jString;
  21. typedef long jInt;
  22. typedef long jReal;

  23. typedef void        (jAPI *jpAddNative)(void *routine, char *name, char *prototype);
  24. typedef jString (jAPI *jpStrMap)        (char *str);
  25. typedef char *        (jAPI *jpStrGet)        (jString strid);

  26. jpAddNative                jAddNative;
  27. jpStrMap                jStrMap;
  28. jpStrGet                jStrGet;

  29. #pragma warning ( disable : 4996 )


  30. jString jNATIVE test(jString js, char *fnname)
  31. {
  32.         char *s;
  33.         s = jStrGet(js);
  34.         s[0] = 'A';
  35.         return jStrMap(s);
  36. }

  37. BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
  38. {
  39.         if (ul_reason_for_call == DLL_PROCESS_ATTACH)
  40.         {
  41.                 DisableThreadLibraryCalls(hModule);

  42.                 HMODULE hjApi = GetModuleHandle("japi.dll");

  43.                 jAddNative        = (jpAddNative)GetProcAddress(hjApi, "jAddNative");
  44.                 jStrMap                = (jpStrMap)*GetProcAddress(hjApi, "jStrMap");
  45.                 jStrGet                = (jpStrGet)*GetProcAddress(hjApi, "jStrGet");

  46.                 jAddNative(test, "test", "(S)S");
  47.         }
  48.     return TRUE;
  49. }

复制代码


在这里有两点很重要



  1. jString jNATIVE test(jString js, char *fnname)
  2. {
  3.         char *s;
  4.         s = jStrGet(js);
  5.         s[0] = 'A';
  6.         return jStrMap(s);
  7. }

复制代码





  1. jAddNative(test, "test", "(S)S");
复制代码


如果运行时崩溃,并且你没有使用Borland的编译器,试一下用__fasecall代替__msfastcall。

  我们的测试函数接收一个string的参数并返回一个string。它仅仅简单的用'A'代替string中的第一个字符,并且无法处理传递的空的string。此外,引擎会将调用的函数名传递给本地,所以我们要将函数名包括在内以确保当我们完成时堆栈被释放。

  jAddNative函数的第三个参数给出了类型信息,括号内就是参数,如(ISI)代表integer x,string s,integer y。在括号后是返回类型,在我们的例子中是s,即string。

  我们需要编译成一个DLL,用一个命令行:

  1. bcc32 -WD -e"test.xjp" test.cpp
复制代码


-WD表示build一个DLL,-e后是输出的文件,test.cpp是输入的源文件。文件扩展名是.xjp而不是.dll,因为这样才能被jAPI识别并链接如War3。现在将test.xjp复制到jAPI将要检查,也就是War3目录下的jNatives文件夹中(如果不复制则留空)。

  接下来我们将本地函数添加到common.j。如果你没有它的一份副本,可以用mpq工具从war3patch.mpq中解出来。

  1. native test takes string s returns string
复制代码


现在从jAPI包中启动LoaderWorldEditor.exe。如果什么也没有发生,可能是war3的注册信息问题,重装下War3试试。如果崩溃了,很可能是你设定的函数的调用约定出了问题,你可以将错误信息发送给我看看。好,现在运行WE,创建一张新地图,做任何您想做的事,将你修改过的common.j导入,并把它的路径从war3mapImported\改为Scripts\来确保它把默认的common.j覆盖。如果几次保存过后你仍然得到本地函数声明问题,检查一下你在common.j中设置的类型和在jAddNative中设置的类型是否匹配。

  如果一切OK,写一些JASS来测试test函数。试一下游戏开始几秒后执行BJDebugMsg(test("Hello"))。保存地图。如果仍出现本地函数的声明问题,检查common.j的原型。如果保存无误,不要动“测试地图”按钮。运行jAPI LoaderWar3.exe代替,或者用命令行-window来运行会更合适。选择你的地图并开始、如果选中地图后没有显示玩家信息,很有可能你在.xjp文件中丢失了一些本地函数。所有你添加到common.j中的本地函数在你的自定义DLL中都要有入口。

  如果游戏运行后显示"Aello",恭喜,你完成了你的第一个自定义本地函数。现在让我们做一些更有趣的事。动态分配内存的缺乏是JASS编程者的一个共同的遗憾。我们甚至不能传递数组参数!在C中,我们可以轻松的用malloc()来分配一大块内存。这有些危险,因为这些分配的内存并不会随着地图结束而释放。尽管我们需要手动释放,但它依然不失为一个好的实践。简单起见,并且我们有return bug来协调,我们仅仅需要用integer来实现数组。设想我们需要4个函数:

  1. native ArrayAlloc takes integer length returns integer
  2. native ArrayFree takes integer arr returns nothing
  3. native ArrayGet takes integer arr, integer index returns integer
  4. native ArraySet takes integer arr, integer index, integer val returns nothing
复制代码


将它们添加到common.j。
接下来复制test.cpp到array.cpp并且用上面的数组本地函数代替原来的test本地函数

  1. jInt jNATIVE ArrayAlloc(jInt len, char *fnname)
  2. {
  3.         return (jInt)malloc(sizeof(jInt)*len);
  4. }

  5. jInt jNATIVE ArraySet(jInt array,jInt index,jInt val, char *fnname)
  6. {
  7.         int *p = (int *)array;
  8.         p[index] = val;
  9.         return p[index];
  10. }

  11. jInt jNATIVE ArrayGet(jInt array, jInt index, char *fnname) {
  12.         int *p = (int *) array;
  13.         return p[index];
  14. }

  15. jInt jNATIVE ArrayFree(jInt array, char *fnname) {
  16.         int *p = (int *)array;
  17.         free(p);
  18.         return 0;
  19. }
复制代码


在所有的函数中我都返回了一些值,因为JASS似乎希望所有的函数都有返回值,即使函数什么也没有返回。
要给以上的数组本地函数添加注册信息,我们要写以下几行:

  1.                 jAddNative(ArrayAlloc,"ArrayAlloc","(I)I");
  2.                 jAddNative(ArraySet,"ArraySet","(III)I");
  3.                 jAddNative(ArrayGet,"ArrayGet","(II)I");
  4.                 jAddNative(ArrayFree,"ArrayFree","(I)I");
复制代码


用 bcc32 -WD -e "array.xjp" array.cpp 来编译,并用array.xjp代替jNatives文件夹下的test.xjp
测试一些简单的代码,如:

  1. local integer heap = ArrayAlloc(16)
  2. call ArraySet(heap,5,42)
  3. call BJDebugMsg(I2S(ArrayGet(heap,5)))
  4. call ArrayFree(heap)
复制代码


如果有问题,可以参考上面test实例中的错误信息
练习:为数组添加边界检测和动态调整大小
以下是另一些本地函数,和他的迷宫生成地图,一个不相交集

  1. typedef struct {
  2.         int parent;
  3.         int rank;
  4. } node;

  5. jInt jNATIVE DJSNew(jInt len, char *fnname)
  6. {
  7.         node *set = (node *)malloc(sizeof(node)*len);
  8.         int i;
  9.         for(i=0;i<len;i++) {
  10.                 set[i].parent = -1;
  11.                 set[i].rank = 0;
  12.         }
  13.         return (int)set;
  14. }

  15. jInt jNATIVE DJSFind(jInt iset, jInt x, char *fnname) {
  16.         node *set = (node *)iset;
  17.         if (set[x].parent == -1) return x;
  18.         set[x].parent = DJSFind(iset,set[x].parent);
  19.         return set[x].parent;
  20. }

  21. jInt jNATIVE DJSUnion(jInt iset,jInt x,jInt y, char *fnname)
  22. {
  23.         node *set = (node *)iset;
  24.         int xr = DJSFind(iset,x);
  25.         int yr = DJSFind(iset,y);
  26.         if(set[xr].rank > set[yr].rank)
  27.                 set[yr].parent = xr;
  28.         else if(set[xr].rank < set[yr].rank)
  29.                 set[xr].parent = yr;
  30.         else {
  31.                 set[yr].parent = xr;
  32.                 set[xr].rank++;
  33.         }
  34.         return 0;
  35. }

  36. jInt jNATIVE DJSFree(jInt iset, char *fnname) {
  37.         free((node *)iset);
  38.         return 0;
  39. }
复制代码

一个比较常用的结构pair和配套函数

  1. typedef struct {
  2.         int car;
  3.         int cdr;
  4. } pair;

  5. pair *allocpair() {
  6.         return (pair *)malloc(sizeof(pair));
  7. }

  8. void freepair(pair *p) {
  9.         free(p);
  10. }

  11. jInt jNATIVE cons(jInt x, jInt y, char *fnname) {
  12.         pair *newpair = allocpair();
  13.         newpair->car = x;
  14.         newpair->cdr = y;
  15.         return (jInt)newpair;
  16. }

  17. jInt jNATIVE car(jInt ipair, char *fnname) {
  18.         pair *p = (pair *)ipair;
  19.         return p->car;
  20. }
  21. jInt jNATIVE cdr(jInt ipair, char *fnname) {
  22.         pair *p = (pair *)ipair;
  23.         return p->cdr;
  24. }
复制代码


原型字母表:

  1. Integer                I
  2. Real                R
  3. String                S
  4. Code                C
  5. Boolean                B
  6. Returns nothing        V
  7. Handles                H; (?)
  8. Handle ext.        H followed by ext followed by semicolon e.g. Hplayer;
复制代码

非常感谢xttocs所做的所有努力和你的阅读。如果你设法用其他不同的编译器做这些工作,或者想用Handle做一些事或者其他任何实际问题,请发送消息!祝你好运。
---------
一些好的编译器
Borland 5.60
Borland 5.5(免费)
---------
重要更新:每个本地函数的最后需要一个额外的"char * fnname"参数。War3会把调用的本地函数名传递进来,我们需要它来确保栈被释放。感谢xttocs。
如果你的stock loader有问题,试一下loader.rar中的那个。

5

主题

134

帖子

278

积分

版主

Rank: 7Rank: 7Rank: 7

积分
278
发表于 2013-2-3 00:42:03 | 显示全部楼层
是啊,增强游戏的功能,,,由于游戏本身没有提供相关的内挂接口,于是可以反工程得到其的进程hook apis,,做成以这个apis为增加的内挂系统,,这在很多游戏中已经也有了,比如ffxi的windower之类,raknet的hook apis,,,其实它们都是引擎功能的修改扩展接口。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-2-27 02:45

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表