|
相信应该有不少人看见过这个帖子,那么有兴趣的人也许可以自己试试啊,做的好的话,可能会成为一个流行的增强外挂呢(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
- /*
- The following routines are all exported by japi.dll:
- char* MemStrSearch(unsigned char *start, unsigned char *end, char *str);
- bool MemPatternCompare(unsigned char *address, unsigned char *pattern, unsigned char *mask, unsigned long length);
- unsigned char* MemPatternSearch(unsigned char *start, unsigned char *end, unsigned char *pattern, unsigned char *mask, unsigned long length);
- PIMAGE_SECTION_HEADER GetImageSectionHeaders(HMODULE hModule, WORD *count);
- PIMAGE_SECTION_HEADER GetImageSectionHeader(HMODULE hModule, unsigned char name[8]);
- void jAPI jBindNative(void *routine, char *name, char *prototype);
- void jAddNative(void *routine, char *name, char *prototype);
- jString jAPI jStrMap(char *str);
- char* jAPI jStrGet(jString strid);
- */
- // - Andy Scott aka xttocs
- #include <windows.h>
- #include <stdio.h>
- #define jNATIVE __stdcall
- #define jAPI __msfastcall
- #define FloatAsInt(f) (*(long*)&f)
- #define IntAsFloat(i) (*(float*)&i)
- typedef long jString;
- typedef long jInt;
- typedef long jReal;
- typedef void (jAPI *jpAddNative)(void *routine, char *name, char *prototype);
- typedef jString (jAPI *jpStrMap) (char *str);
- typedef char * (jAPI *jpStrGet) (jString strid);
- jpAddNative jAddNative;
- jpStrMap jStrMap;
- jpStrGet jStrGet;
- #pragma warning ( disable : 4996 )
- jString jNATIVE test(jString js, char *fnname)
- {
- char *s;
- s = jStrGet(js);
- s[0] = 'A';
- return jStrMap(s);
- }
- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
- {
- if (ul_reason_for_call == DLL_PROCESS_ATTACH)
- {
- DisableThreadLibraryCalls(hModule);
- HMODULE hjApi = GetModuleHandle("japi.dll");
- jAddNative = (jpAddNative)GetProcAddress(hjApi, "jAddNative");
- jStrMap = (jpStrMap)*GetProcAddress(hjApi, "jStrMap");
- jStrGet = (jpStrGet)*GetProcAddress(hjApi, "jStrGet");
- jAddNative(test, "test", "(S)S");
- }
- return TRUE;
- }
复制代码
在这里有两点很重要
- jString jNATIVE test(jString js, char *fnname)
- {
- char *s;
- s = jStrGet(js);
- s[0] = 'A';
- return jStrMap(s);
- }
复制代码
和
- jAddNative(test, "test", "(S)S");
复制代码
如果运行时崩溃,并且你没有使用Borland的编译器,试一下用__fasecall代替__msfastcall。
我们的测试函数接收一个string的参数并返回一个string。它仅仅简单的用'A'代替string中的第一个字符,并且无法处理传递的空的string。此外,引擎会将调用的函数名传递给本地,所以我们要将函数名包括在内以确保当我们完成时堆栈被释放。
jAddNative函数的第三个参数给出了类型信息,括号内就是参数,如(ISI)代表integer x,string s,integer y。在括号后是返回类型,在我们的例子中是s,即string。
我们需要编译成一个DLL,用一个命令行:
- 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中解出来。
- 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个函数:
- native ArrayAlloc takes integer length returns integer
- native ArrayFree takes integer arr returns nothing
- native ArrayGet takes integer arr, integer index returns integer
- native ArraySet takes integer arr, integer index, integer val returns nothing
复制代码
将它们添加到common.j。
接下来复制test.cpp到array.cpp并且用上面的数组本地函数代替原来的test本地函数
- jInt jNATIVE ArrayAlloc(jInt len, char *fnname)
- {
- return (jInt)malloc(sizeof(jInt)*len);
- }
- jInt jNATIVE ArraySet(jInt array,jInt index,jInt val, char *fnname)
- {
- int *p = (int *)array;
- p[index] = val;
- return p[index];
- }
- jInt jNATIVE ArrayGet(jInt array, jInt index, char *fnname) {
- int *p = (int *) array;
- return p[index];
- }
- jInt jNATIVE ArrayFree(jInt array, char *fnname) {
- int *p = (int *)array;
- free(p);
- return 0;
- }
复制代码
在所有的函数中我都返回了一些值,因为JASS似乎希望所有的函数都有返回值,即使函数什么也没有返回。
要给以上的数组本地函数添加注册信息,我们要写以下几行:
- jAddNative(ArrayAlloc,"ArrayAlloc","(I)I");
- jAddNative(ArraySet,"ArraySet","(III)I");
- jAddNative(ArrayGet,"ArrayGet","(II)I");
- jAddNative(ArrayFree,"ArrayFree","(I)I");
复制代码
用 bcc32 -WD -e "array.xjp" array.cpp 来编译,并用array.xjp代替jNatives文件夹下的test.xjp
测试一些简单的代码,如:
- local integer heap = ArrayAlloc(16)
- call ArraySet(heap,5,42)
- call BJDebugMsg(I2S(ArrayGet(heap,5)))
- call ArrayFree(heap)
复制代码
如果有问题,可以参考上面test实例中的错误信息
练习:为数组添加边界检测和动态调整大小
以下是另一些本地函数,和他的迷宫生成地图,一个不相交集
- typedef struct {
- int parent;
- int rank;
- } node;
- jInt jNATIVE DJSNew(jInt len, char *fnname)
- {
- node *set = (node *)malloc(sizeof(node)*len);
- int i;
- for(i=0;i<len;i++) {
- set[i].parent = -1;
- set[i].rank = 0;
- }
- return (int)set;
- }
- jInt jNATIVE DJSFind(jInt iset, jInt x, char *fnname) {
- node *set = (node *)iset;
- if (set[x].parent == -1) return x;
- set[x].parent = DJSFind(iset,set[x].parent);
- return set[x].parent;
- }
- jInt jNATIVE DJSUnion(jInt iset,jInt x,jInt y, char *fnname)
- {
- node *set = (node *)iset;
- int xr = DJSFind(iset,x);
- int yr = DJSFind(iset,y);
- if(set[xr].rank > set[yr].rank)
- set[yr].parent = xr;
- else if(set[xr].rank < set[yr].rank)
- set[xr].parent = yr;
- else {
- set[yr].parent = xr;
- set[xr].rank++;
- }
- return 0;
- }
- jInt jNATIVE DJSFree(jInt iset, char *fnname) {
- free((node *)iset);
- return 0;
- }
复制代码
一个比较常用的结构pair和配套函数
- typedef struct {
- int car;
- int cdr;
- } pair;
- pair *allocpair() {
- return (pair *)malloc(sizeof(pair));
- }
- void freepair(pair *p) {
- free(p);
- }
- jInt jNATIVE cons(jInt x, jInt y, char *fnname) {
- pair *newpair = allocpair();
- newpair->car = x;
- newpair->cdr = y;
- return (jInt)newpair;
- }
- jInt jNATIVE car(jInt ipair, char *fnname) {
- pair *p = (pair *)ipair;
- return p->car;
- }
- jInt jNATIVE cdr(jInt ipair, char *fnname) {
- pair *p = (pair *)ipair;
- return p->cdr;
- }
复制代码
原型字母表:
- Integer I
- Real R
- String S
- Code C
- Boolean B
- Returns nothing V
- Handles H; (?)
- 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中的那个。 |
|