模块说明

  1. 模块名称:mygeoip
  2. 模块功能:当玩家在控制台输入myip时,会在屏幕上打印出他们的IP信息和地理位置
  3. 模块实现:分为两部分,一是module从plugin接收ip字符串参数,根据ip字符串从qqwry.dat文件中解析出ip的地理位置信息返回给plugin,二是plugin监听玩家在控制台输入的myip命令,根据玩家的id获取其ip字符串,作为参数传递给module

Module编写

初始化模块

1
2
# pwd == sdk/amxmodx/modules
git clone https://github.com/wudics/mymodule2.git mygeoip

我们这里通过一个template来初始化我们的module项目

修改模块名称

用编辑器打开mygeoip/AMBuilder文件,找到下面的代码:

1
binary = AMXX.MetaModule(builder, 'mymodule2')

把mymodule2修改成mygeoip,这里修改的是编译生成后的模块名称。

1
binary = AMXX.MetaModule(builder, 'mygeoip')

修改模块配置

用编辑器打开mygeoip/moduleconfig.h文件,找到OnAmxxAttach,将前边的注释去掉,表示开启此项功能。

1
#define FN_AMXX_ATTACH OnAmxxAttach

OnAmxxAttach如字面意思,就是当amxx加载完成后,执行的回调函数。官方建议在这里注册native函数。

然后,配置模块显示的信息,如下:

1
2
3
4
5
6
7
8
// Module info
#define MODULE_NAME "My Geo IP"
#define MODULE_VERSION AMXX_VERSION
#define MODULE_AUTHOR "Laura Mercia"
#define MODULE_URL "https://www.wudics.com"
#define MODULE_LOGTAG "MYGEOIP"
#define MODULE_LIBRARY "mygeoip"
#define MODULE_LIBCLASS ""

代码编写

引入qqwry函数库

这里为了方便演示,直接引入qqwry.h和qqwry.cpp文件,来实现qqwry.dat文件数据的读取,把qqwry.cpp和qqwry.h文件拷贝到modules/mygeoip目录下,然后打开mygeoip/AMBuilder文件,把qqwry.cpp添加到编译源中。

1
2
3
4
5
binary.sources = [
'../../public/sdk/amxxmodule.cpp',
'qqwry.cpp',
'mymodule2.cpp',
]

这个库能够根据ip字符串,从qqwry.dat文件中解析出ip的地址位置信息,实现gkb到utf8编码的转换,这个库对外提供了一个getipinfo函数:

1
2
3
4
5
6
7
8
9
10
typedef struct TYPEIPINFO {
unsigned int ipstart;
unsigned int ipend;
unsigned char adata[256];
unsigned char adata_utf8[256];
unsigned char bdata[256];
unsigned char bdata_utf8[256];
} IPINFO;

void getipinfo(IPINFO *info, unsigned char *ip);

使用方法:我们声明一个IPINFO结构体来存储解析出来的IP地理位置信息,与ip字符串传入getipinfo即可。

声明native函数

在mymodule2.h中,首先引入必要的头文件,包括stdio、string、stdlib等标准库头文件,还有amxxmodule、qqwry等头文件,其中amxxmodule.h是开发模块时必需的,qqwry.h是我们之前引入的用于读取ip信息的第三方库。

然后,声明一个名为getIpInfoByStr的native函数,以及一个名为nativesInfo的native注册信息结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mymodule2.h

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <amxxmodule.h>

#include "qqwry.h"

static cell AMX_NATIVE_CALL getIpInfoByStr(AMX *amx, cell *params);

AMX_NATIVE_INFO nativesInfo[] = {
// {name, function}
{"getIpForPlugin", getIpInfoByStr},
// always be NULL in the end.
{NULL, NULL}
};

其中getIpForPlugin是我们的native函数名称,这个名称将会在编写plugin时使用。而getIpInfoByStr是指向在module中定义的native函数指针,需要在module中实现函数代码。

注册native函数

在mymodule2.cpp中,实现OnAmxxAttach回调函数,在回调中,注册我们的native函数,这样我们在plugin中才能通过getIpForPlugin这个名称来找到并调用我们的getIpInfoByStr函数。

1
2
3
4
5
6
7
8
// mymodule2.cpp

#include "mymodule2.h"

void OnAmxxAttach()
{
MF_AddNatives(nativesInfo);
}

实现native函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// mymodule2.cpp

static cell AMX_NATIVE_CALL getIpInfoByStr(AMX *amx, cell *params)
{
int numParams = *params / sizeof(cell);
if (numParams < 1) {
return 0;
}

int len = 0;
char *ip = strdup(MF_GetAmxString(amx, params[1], 0, &len));

IPINFO info;
getipinfo(&info, (unsigned char *)ip); // 由qqwry.h提供

unsigned char strIpStart[16];
unsigned char strIpEnd[16];
uint2ip(strIpStart, info.ipstart); // 由qqwry.h提供
uint2ip(strIpEnd, info.ipend); // 由qqwry.h提供

/*
* 我们在plugin脚本中定义的native函数原型是这样子的
* native getIpForPlugin(const ipStr[], retSIP[], retEIP[], retA, retB);
* retSIP,对应params[2],用于保存起始IP(cell类型)
* retEIP,对应params[3],用于保存终止IP(cell类型)
* retA,对应params[4],用于保存utf8编码的省份信息字符串(cell类型)
* retB,对应params[5],用于保存utf8编码的城市地址信息字符串(cell类型)
* 所以我们通过MF_SetAmxString把char类型的字符串转化为cell类型,在写回到参数中
*/
MF_SetAmxString(amx, params[2], (char *)strIpStart, 16);
MF_SetAmxString(amx, params[3], (char *)strIpEnd, 16);
MF_SetAmxString(amx, params[4], (char *)info.adata_utf8, 256);
MF_SetAmxString(amx, params[5], (char *)info.bdata_utf8, 256);

if (ip != NULL) free(ip);

return 1;
}

编译module

打开amxmodx/AMBuildScript文件,在RunBuildScripts中加入我们的模块构建脚本路径。

1
2
3
4
5
6
7
8
9
builder.RunBuildScripts(
[
'amxmodx/AMBuilder',
'compiler/amxxpc/AMBuilder',
...
'modules/mygeoip/AMBuilder',
],
{ 'AMXX': AMXX }
)

modules/mygeoip/AMBuilder就是我们的模块构建脚本路径了。

最后使用ambuild命令编译即可得到我们的模块文件,我这里使用windows,所以模块文件是mygeoip_amxx.dll,linux的话就是mygeoip_amxx_i386.so。

Plugin编写

我们这里使用官方提供的IDE工具AMXX_Studio来编写我们Plugin的sma源码,同时也用它来编译sma源码得到我们的*.amxx插件。

首先,我们先创建myip.sma源文件,然后把以下代码复制到我们的sma文件,最后编译即可得到myip.amxx文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// myip.sma

#include <amxmodx>
#include <amxmisc>

#define PLUGIN "IP INFO Plug-In"
#define VERSION "1.0"
#define AUTHOR "Wudics"

native getIpForPlugin(const ipStr[], retSIP[], retEIP[], retA[], retB[]);

public plugin_init() {
register_plugin(PLUGIN, VERSION, AUTHOR)

// Add your code here...
register_clcmd("myip", "ipsearch")
}

public ipsearch(id) {
new szIP[32]
get_user_ip(id, szIP, charsmax(szIP), 1)

new SIP[16], EIP[16], retA[256], retB[256]

// call native
getIpForPlugin(szIP, SIP, EIP, retA, retB)

client_print(id, print_chat, "IP from %s to %s^rLocation is %s %s", SIP, EIP, retA, retB)

return PLUGIN_HANDLED
}

首先在plugin_init()中,初始化插件信息,然后使用register_clcmd注册我们的myip命令,当玩家在控制台输入myip时,就会调用我们的ipsearch(id),并传入玩家id号参数。

然后在ipsearch(id)中,我们通过id获取玩家的ip地址,然后把ip地址,以及用于接收ip信息的变量,作为参数,传入getIpForPlugin()函数,这个函数就是我们之前在module时,注册的native,在plugin中要使用的话,我们需要先用native关键字先声明。

1
native getIpForPlugin(const ipStr[], retSIP[], retEIP[], retA[], retB[]);

最后把得到的ip信息在玩家的屏幕左下角显示出来。

部署测试

  1. 把mygeoip_amxx.dll放置到服务器的cstrike\addons\amxmodx\modules目录下,修改cstrike\addons\amxmodx\configs下的modules.ini文件,在末尾处另起一行,加上mygeoip。
  2. 把myip.amxx放置到服务器的cstrike\addons\amxmodx\plugins目录下,修改cstrike\addons\amxmodx\configs下的plugins.ini文件,在末尾处另起一行,加上myip.amxx。
  3. 把qqwry.dat放置到服务器的cstrike\addons\amxmodx\data目录下即可(这个路径可以在qqwry.h中配置)。
  4. 运行服务器,然后打开客户端,加入游戏,按~键呼出控制台,输入myip,然后关闭控制台看看是否在左下角显示出了你的ip信息。

参考文献

AMX Mod X Documentation
纯真IP库QQWry.dat文件解析