【POE 应用程序实例】决战脱机外挂

【POE 应用程序实例】决战脱机外挂

运行效果:

[Copy to clipboard] [ - ]
CODE:
D:\MoChou\droiyan>client.pl
决战文本客户端 0.91 版第 78 次修订版。

正在加载物品信息表……
加载完成。
03-13 18:17:34 连接成功: Session Manager Server 连接成功。
03-13 18:17:34 正在登录: 已发送登录请求。
03-13 18:17:34 正在登录: 接收到登录响应报文。
03-13 18:17:34 登录成功: 登录成功。
03-13 18:17:34 开始游戏: 已发送开始游戏请求。
03-13 18:17:34 开始游戏: 接收到连接信息报文。
03-13 18:17:34 开始游戏: 用户名: [flw] 服务器个数: [1]
03-13 18:17:34 开始游戏: IP: [flw masked] Port: [9000]
03-13 18:17:34 正在连接: 正在连接 CharInfoServer ……
03-13 18:17:34 连接成功: CharInfoServer 连接成功。
03-13 18:17:34 请求公钥: 正在请求加密公钥……
03-13 18:17:35 请求公钥: 接收到公钥响应报文。
03-13 18:17:35 请求公钥: 公钥: a1a5e326d7297da9
03-13 18:17:35 得到公钥: 已经获得加密公钥。
03-13 18:17:35 登录游戏: 已发送登录游戏请求。
03-13 18:17:35 登录游戏: 接收到加密报文,自动解密……
03-13 18:17:35 登录游戏: 报文已解密。包序号:[1]。
03-13 18:17:35 登录游戏: 接收到帐户登录结果报文。
03-13 18:17:35 登录成功: 登录成功。角色信息:
===============================================================================
角色: [flw1] 性别: [男] 职业: [枪手] 级别: [104]
力量: [47] 体质: [48] 敏捷: [49] 智慧: [50] 智力: [51]
生命: [312/536] 精神: [584/584] 体力: [224/224]
===============================================================================
03-13 18:17:35 断开连接: 已发送断开角色服务器请求。
03-13 18:17:35 断开连接: 接收到加密报文,自动解密……
03-13 18:17:35 断开连接: 报文已解密。包序号:[2]。
03-13 18:17:35 断开连接: 接收到断开角色服务器响应报文。
03-13 18:17:35 断开连接: 断开角色服务器
03-13 18:17:35 进入游戏: 正在连接 ZoneServer ……
03-13 18:17:35 连接成功: ZoneServer 连接成功。
03-13 18:17:35 请求公钥: 正在请求加密公钥……
03-13 18:17:35 请求公钥: 接收到公钥响应报文。
03-13 18:17:35 请求公钥: 公钥: a1a5e326d7297da9
03-13 18:17:35 得到公钥: 已经获得加密公钥。
03-13 18:17:35 进入游戏: 已发送进入游戏请求。
03-13 18:17:35 进入游戏: 接收到加密报文,自动解密……
03-13 18:17:35 进入游戏: 报文已解密。包序号:[1]。
03-13 18:17:35 进入游戏: 接收到压缩报文,自动解压……
03-13 18:17:35 进入游戏: 报文已解压。长度: [1036]。
03-13 18:17:35 进入游戏: 接收到人物信息通知报文。
03-13 18:17:35 进入游戏: 接收到人物 flw1 的详细信息。
03-13 18:17:35 进入游戏: 接收到加密报文,自动解密……
03-13 18:17:35 进入游戏: 报文已解密。包序号:[2]。
03-13 18:17:35 进入游戏: 接收到进入游戏响应报文。
03-13 18:17:35 进入游戏: 断开 Session Manager Server。
03-13 18:17:35 普通在线: 已进入游戏。
03-13 18:17:35 普通在线: 位置:[390, 1328]; 面向:[7]; 游戏时间:[8 点 10 分]
03-13 18:17:35 普通在线: 服用药品 生命回复剂A
03-13 18:17:35 普通在线: 8 点了。
03-13 18:17:35 普通在线: 接收到人物 flw1 的概要信息。
03-13 18:17:35 普通在线: 发现了一个怪物,名称 [低等异形] HP [65] 位置 [368,1312]
03-13 18:17:35 普通在线: 有 1 个怪物信息。
03-13 18:17:35 普通在线: 接收到一般消息: [欢迎来到游戏天地]
03-13 18:17:35 普通在线: 接收到一般消息: [http://url/flw/masked]
03-13 18:17:35 正在移动: 跑动 cur: 390,1328 next1: 389,1327 next2: 387,1327
03-13 18:17:36 正在移动: 物品使用结果: 生命恢复到 372。
03-13 18:17:36 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:36 正在移动: 10007 经由 389, 1327 跑到 387, 1327
03-13 18:17:36 正在移动: 跑动 cur: 387,1327 next1: 386,1326 next2: 385,1325
03-13 18:17:36 正在移动: 体力减少了 1,目前是 223/224
03-13 18:17:36 正在移动: 服用药品 生命回复剂A
03-13 18:17:37 正在移动: 10007 经由 386, 1326 跑到 385, 1325
03-13 18:17:37 正在移动: 跑动 cur: 385,1325 next1: 384,1324 next2: 383,1323
03-13 18:17:37 正在移动: 体力减少了 1,目前是 222/224
03-13 18:17:37 正在移动: 服用药品 生命回复剂A
03-13 18:17:37 正在移动: 物品使用结果: 生命恢复到 432。
03-13 18:17:37 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:38 正在移动: 10007 经由 384, 1324 跑到 383, 1323
03-13 18:17:38 正在移动: 跑动 cur: 383,1323 next1: 382,1322 next2: 381,1321
03-13 18:17:38 正在移动: 体力减少了 1,目前是 221/224
03-13 18:17:38 正在移动: 服用药品 生命回复剂A
03-13 18:17:38 正在移动: 物品使用结果: 生命恢复到 492。
03-13 18:17:38 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:39 正在移动: 10007 经由 382, 1322 跑到 381, 1321
03-13 18:17:40 正在移动: 跑动 cur: 381,1321 next1: 379,1321 next2: 378,1320
03-13 18:17:40 正在移动: 20752 嗖~地一声消失了……
03-13 18:17:40 正在移动: 体力减少了 1,目前是 220/224
03-13 18:17:40 正在移动: 服用药品 生命回复剂A
03-13 18:17:40 正在移动: 物品使用结果: 生命恢复到 552。
03-13 18:17:40 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:40 正在移动: 10007 经由 379, 1321 跑到 378, 1320
03-13 18:17:40 正在移动: 跑动 cur: 378,1320 next1: 377,1319 next2: 376,1318
03-13 18:17:40 正在移动: 体力减少了 1,目前是 219/224
03-13 18:17:40 正在移动: 服用药品 生命回复剂A
03-13 18:17:41 正在移动: 物品使用结果: 生命恢复到 612。
03-13 18:17:41 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:41 正在移动: 10007 经由 377, 1319 跑到 376, 1318
03-13 18:17:41 正在移动: 跑动 cur: 376,1318 next1: 375,1317 next2: 374,1316
03-13 18:17:41 正在移动: 体力减少了 1,目前是 218/224
03-13 18:17:41 正在移动: 服用药品 生命回复剂A
03-13 18:17:41 正在移动: 物品使用结果: 生命恢复到 626。
03-13 18:17:41 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:42 正在移动: 10007 经由 375, 1317 跑到 374, 1316
03-13 18:17:42 正在移动: 跑动 cur: 374,1316 next1: 372,1316 next2: 371,1315
03-13 18:17:42 正在移动: 体力减少了 1,目前是 217/224
03-13 18:17:42 正在移动: 服用药品 生命回复剂A
03-13 18:17:42 正在移动: 物品使用结果: 生命恢复到 626。
03-13 18:17:42 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:43 正在移动: 10007 经由 372, 1316 跑到 371, 1315
03-13 18:17:43 正在移动: 跑动 cur: 371,1315 next1: 370,1314 next2: 369,1313
03-13 18:17:43 正在移动: 体力减少了 1,目前是 216/224
03-13 18:17:43 正在移动: 服用药品 生命回复剂A
03-13 18:17:43 正在移动: 物品使用结果: 生命恢复到 626。
03-13 18:17:43 正在移动: flw1 吃下了 生命恢复剂
03-13 18:17:44 正在移动: 10007 经由 370, 1314 跑到 369, 1313
03-13 18:17:44 普通在线: 体力减少了 1,目前是 215/224
03-13 18:17:44 普通在线: 服用药品 生命回复剂A
03-13 18:17:44 普通在线: 打 20644
03-13 18:17:44 普通在线: 攻击 20644
03-13 18:17:44 普通在线: 物品使用结果: 生命恢复到 626。
03-13 18:17:44 普通在线: flw1 吃下了 生命恢复剂
03-13 18:17:45 普通在线: 物品使用结果: 生命恢复到 626。
03-13 18:17:45 普通在线: flw1 吃下了 生命恢复剂
03-13 18:17:45 普通在线: 你想打 20644,结果它灵巧地躲开了
03-13 18:17:45 普通在线: 打 20810
03-13 18:17:45 普通在线: 攻击 20810
03-13 18:17:46 普通在线: 20810 想打你,结果你灵巧地躲开了
03-13 18:17:46 普通在线: 你打中了 20810, 它的 HP: [48/70]
03-13 18:17:46 普通在线: 攻击 20644
03-13 18:17:47 普通在线: 生命值增加了 307,目前是 619/626
03-13 18:17:47 普通在线: 20644 打中了你, 你的 HP: [619/626]
03-13 18:17:47 普通在线: 你打中了 20644, 它的 HP: [44/65]
03-13 18:17:47 普通在线: 20810 想打你,结果你灵巧地躲开了
03-13 18:17:47 普通在线: 攻击 20810
03-13 18:17:48 普通在线: 你想打 20810,结果它灵巧地躲开了
03-13 18:17:48 普通在线: 打 20769
03-13 18:17:48 普通在线: 攻击 20769
03-13 18:17:48 普通在线: 生命值减少了 7,目前是 612/626
03-13 18:17:48 普通在线: 20644 打中了你, 你的 HP: [612/626]
03-13 18:17:48 普通在线: 20810 想打你,结果你灵巧地躲开了
03-13 18:17:48 普通在线: 攻击 20644
03-13 18:17:49 普通在线: 生命值减少了 6,目前是 606/626
03-13 18:17:49 普通在线: 20769 打中了你, 你的 HP: [606/626]
03-13 18:17:49 普通在线: 你打中了 20769, 它的 HP: [47/70]
Terminating on signal SIGINT(2)

主程序:

[Copy to clipboard] [ - ]
CODE:
use strict;
use warnings;

# 加载 POE 以及本程序中所使用到的组件
# 之所以要写这么多是因为有些动态加载的模块 PerlAPP 识别不到,
# 编译后缺少东西不能正常运行,所以必须得写明了。
use POE;
use POE::Session;
use POE::Loop::Select;
use POE::Wheel::SocketFactory;
use POE::Wheel::ReadWrite;
use POE::Driver::SysRW;
use POE::Filter::Line;
use POE::Filter::Stream;
use POE::Resource::Aliases;
use POE::Resource::Events;
use POE::Resource::Extrefs;
use POE::Resource::FileHandles;
use POE::Resource::SIDs;
use POE::Resource::Sessions;
use POE::Resource::Signals;
use POE::Resource::Statistics;
use POE::Resource::Controls;
use POE::Wheel;

use JzCrypt;
use GameClient;

use YAML qw(LoadFile DumpFile);

print "决战文本客户端 0.91 版第 $GameClient::VERSION 次修订版。\n\n";

print "正在加载物品信息表……\n";
$GameClient::ddItemInfo = LoadFile( 'items.yaml' );
#use Data::Dumper;
#print Dumper( $GameClient::ddItemInfo );
print "加载完成。\n";
#die;

$GameClient::Debug = 1;

my $session = POE::Session->create(
    inline_states => {
        _start                  => \&game_start,
        _stop                   => \&game_stop,
        sm_connected            => \&sm_connected,
        sm_input                => \&sm_input,
        charinfo_connected      => \&charinfo_connected,
        charinfo_input          => \&charinfo_input,
        zoneserver_connected    => \&zoneserver_connected,
        zoneserver_input        => \&zoneserver_input,
        sock_error              => \&sock_error,
#        out_flushed     => \&server_flush,
    },
    args => [
        @ARGV
    ],
);

our $Game = new GameClient( $session );

POE::Kernel->run();

sub game_start {
    my ($kernel, $heap, $user, $pass, $ip, $port ) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2, ARG3];

    $heap->{user} = $user || 'flw';
    $heap->{pass} = $pass || 'admin';
    $ip = 'flw.masked' unless $ip;
    $port = 23 unless $port;

    $heap->{sm_socket} = POE::Wheel::SocketFactory->new(
        RemoteAddress  => $ip,
        RemotePort     => $port,
        SuccessEvent   => 'sm_connected',
        FailureEvent   => 'sock_error',
    );
}

sub game_stop {
    $Game->logmsg( "游戏退出。" );
}

sub sm_connected {
    my ($kernel, $session, $heap, $commSocket) = @_[KERNEL, SESSION, HEAP, ARG0];

    $heap->{sm_wheel} = new POE::Wheel::ReadWrite(
        Handle       => $commSocket,                # 将已经 connect 成功的 socket 句柄链接到这个轮子上
        Driver       => new POE::Driver::SysRW,     # 使用 sysread/syswrite 来驱动这个轮子
        InputFilter  => new POE::Filter::Stream(),
        OutputFilter => new POE::Filter::Stream,
        InputEvent   => 'sm_input',
        ErrorEvent   => 'sock_error',               # 有错误发生时,产生一个 ``io_error'' 事件。
#        FlushedEvent => 'out_flushed',              # 输出被 flushed 时的事件,不需要处理。
    );  

    $Game->{sm_wheel} = $heap->{sm_wheel};
    $Game->{state} = '连接成功';
    $Game->logmsg( 'Session Manager Server 连接成功。' );
    $Game->Login( $heap->{user}, $heap->{pass} );
}

sub sm_input {
    my ($kernel, $heap, $chunk) = @_[KERNEL, HEAP, ARG0];

    my ($name, $id, $data) = $Game->SMPacketType( $chunk );
    if ( defined $name ){
        $Game->logmsg( "接收到$name报文。" );
        $Game->SMParse( $id, $data );
    }
    else{
        $Game->logmsg( sprintf "接收到未知 [0x%02X:%d] 报文。", $id, $id - 0x8000 );
        $Game->pkgDump( $data, 'From SM:' );
    }
}

sub charinfo_connected {
    my ($kernel, $session, $heap, $commSocket) = @_[KERNEL, SESSION, HEAP, ARG0];

    $heap->{wheel} = new POE::Wheel::ReadWrite(
        Handle       => $commSocket,                # 将已经 connect 成功的 socket 句柄链接到这个轮子上
        Driver       => new POE::Driver::SysRW,     # 使用 sysread/syswrite 来驱动这个轮子
        InputFilter  => new POE::Filter::Stream(),
        OutputFilter => new POE::Filter::Stream,
        InputEvent   => 'charinfo_input',
        ErrorEvent   => 'sock_error',               # 有错误发生时,产生一个 ``io_error'' 事件。
#        FlushedEvent => 'out_flushed',              # 输出被 flushed 时的事件,不需要处理。
    );  

    $Game->{wheel} = $heap->{wheel};
    $Game->{state} = '连接成功';
    $Game->logmsg( 'CharInfoServer 连接成功。' );
    $Game->GetPublicKey(0);
}

sub charinfo_input {
    my ($kernel, $heap, $chunk) = @_[KERNEL, HEAP, ARG0];

    if ( substr( $chunk, 0, 2 ) ne "\xaa\x55"
        or substr( $chunk, -2, 2 ) ne "\x55\xaa" ){
        $Game->logmsg( '报文格式有误。' );
    }

    $chunk = unpack( 's/a*', substr( $chunk, 2, -2 ) );
    my ($name, $id, $data) = $Game->CIPacketType( $chunk );
    if ( defined $name ){
        $Game->logmsg( "接收到$name报文。" );
        $Game->CIParse( $id, $data );
    }
    else{
        $Game->logmsg( sprintf( "接收到未知 [%x] 报文。", $id ) );
        $Game->pkgDump( $data, 'From CI:' );
    }
}

sub zoneserver_connected {
    my ($kernel, $session, $heap, $commSocket) = @_[KERNEL, SESSION, HEAP, ARG0];

    $heap->{wheel} = new POE::Wheel::ReadWrite(
        Handle       => $commSocket,                # 将已经 connect 成功的 socket 句柄链接到这个轮子上
        Driver       => new POE::Driver::SysRW,     # 使用 sysread/syswrite 来驱动这个轮子
        InputFilter  => new POE::Filter::Line( Literal => "\x55\xAA" ),
        OutputFilter => new POE::Filter::Stream,
        InputEvent   => 'zoneserver_input',
        ErrorEvent   => 'sock_error',               # 有错误发生时,产生一个 ``io_error'' 事件。
#        FlushedEvent => 'out_flushed',              # 输出被 flushed 时的事件,不需要处理。
    );  

    $Game->{wheel} = $heap->{wheel};
    $Game->{state} = '连接成功';
    $Game->logmsg( 'ZoneServer 连接成功。' );

    $Game->GetPublicKey(0);
}

sub zoneserver_input {
    my ($kernel, $heap, $chunk) = @_[KERNEL, HEAP, ARG0];

    if ( substr( $chunk, 0, 2 ) ne "\xaa\x55" ){
        # 报文的结束部分 0x55AA 已经被 POE:F:Line 过滤掉了。
        $Game->logmsg( '报文格式有误。' );
        return;
    }

    $chunk = unpack( 's/a*', substr( $chunk, 2 ) );
    my ($name, $id, $data) = $Game->CIPacketType( $chunk );
    if ( defined $name ){
        unless ( $Game->{online} ){ # 玩家在线后就不再提示此类信息。
            $Game->logmsg( "接收到$name报文。" );
        }
        $Game->CIParse( $id, $data );
    }
    else{
        $Game->todo( $id, $data );
        $Game->logmsg( sprintf( "接收到未知 [0x%02X:%d] 报文。", $id, $id ) );
        $Game->logmsg( "请将当前目录的 todo.txt 文件送交开发人员,谢谢合作!" );
    }
}

sub sock_error {
        my ($kernel, $session, $operation, $errnum, $errstr) =
                @_[KERNEL, SESSION, ARG0, ARG1, ARG2];

    {
        local $^E = $errnum;  # $^E 比 $! 的信息更加详细,而且还是本地语言的。
        $errstr = $^E;
    }

    $Game->logmsg( "通讯错误:[$errnum]", $errstr );
}

另外还有一个解压模块,一个解密模块,都是用 C 写的,就不发了。
噫,看上很不错啊。看来以后要看一下POE了, 这样来写client蛮好的。


QUOTE:
原帖由 路小佳 于 2007-3-13 18:35 发表
噫,看上很不错啊。看来以后要看一下POE了, 这样来写client蛮好的。

不光是写 client 好,或者是写 server 好。
POE 适用于任何可以用有穷自动机算法描述的应用。
典型的比如计算器。
封包格式和加解密算法自己分析出来的吗?

原来如此,呵呵,没事了



QUOTE:
原帖由 Namelessxp 于 2007-3-13 18:39 发表
封包格式和加解密算法自己分析出来的吗?

决战的服务端源代码网络上有的,封包格式看源代码就可以看出来。
加密解密算法,还有压缩解压算法,没有源代码。
哈哈,小case
flw你不是对RO也有研究吗
哈哈
支持一下POE,偶还木研究过。
呵呵,perl真的强大到外挂领域都有其身影,呵呵,好几个外挂研究的站点都有提及perl
并有他们几个很牛的外挂研究高手在指点如何学perl来开发外挂。hoho
呵呵,perl的AI居然是在外挂里面得以充分发挥,hoho!
Kore第一讲: Kore运行与编译
Kore第一讲: Kore运行与编译 by ICE-WR


Kore是用perl来编写,目前WIN32系统比较流行的是ActivePerl,网站为http://www.activestate.com/,而如果你需要把Kore编译为exe执行文件,那你需要安装Perl PDK,里面有perlapp用来把.pl文件转为.exe文件

已经安装了ActivePerl和perl PDK了,但为什么还不能运行Kore.pl呢?请打开Kore.pl,你会看到类似这样的语句:

use Time::HiRes qw(time usleep);
use IO::Socket;

use语句用作调用模块,而你没有这些模块那就当然运行不了咯,PDK里包含一个叫VPM(Visual Package Manager)的模块管理软件,运行后会出现一个搜索网页,在搜索栏输入Time::HiRes和IO::Socket就会从网上把模块搜索出来并安装到你的电脑里,只要把模块都装好,那你就可以运行Kore.pl了

如何把.pl文件转为.exe文件呢?这里需要用PDK里的perlapp,在这里用KE的编译来举例:

perlapp --xclude --icon KoreEasy.ico KoreEasy.pl

--xclude 编译时不包含perl58.dll文件,这样会让编译出来的程序小点,但别的机器要运行时需要复制perl58.dll

--icon 这个是指定图标文件

ActivePerl下载地址:http://www.activestate.com/Products/ActivePerl/

Perl PDK下载地址:http://www.activestate.com/Products/Perl_Dev_Kit/



以下为KoreEasy编译的完整语句,包含版本信息

perlapp --info FileVersion=0.8.00.0306roductVersion=0.8.00.0306;FileDe脚本ion=ICE-WR;CompanyName=http://www.mu20.com;LegalCopyright=ICE-WR;LegalTrademarks=ICE-WR;OriginalFilename=KoreEasy.exeroductName=KoreEasy --xclude --verbose --force --icon KoreEasy.ico KoreEasy.pl


我是按着kora的说明配置的Perl和PDK

This is the official homepage of the Kore Project - An open source, complete console-based Ragnarok client. Gravity (company that developed Ragnarok Online) has nothing to do with Kore, this project is unofficial.


What is Kore?

Kore is a console-based Ragnarok client/bot, written in Perl/C.



How do I install Kore?

Kore can be run on Windows and Linux, and if requested, versions can be compiled for Mac operating systems.

Kore is run like any other program. Grab Kore from the download page, unzip, and 执行. Grab the manual too for console commands, chat commands (receieve commands in-game!), and configuration options.


How do I compile Kore?

Windows

Download and install ActivePerl for Windows

Download and install Perl DevKit for windows

Next you must install the necessary Perl packages:

1) Go to Start->Run... and run \'ppm\'
2) In the console 无效 \'install Time::HiRes\'
3) Next 无效 \'install Win32::API\'
4) If it returns with multiple results 无效 \'install 1\'

In the Kore root directory make a file called \"Kore.bat\". In this file put the following line:

perl source\\Kore.pl

To run Kore without compiling double-click on Kore.bat.


In the Kore root directory make a file called \"Kore-compile.bat\". In this file put the following line:

perlapp --force -o=Kore.exe source\\Kore.pl

To compile Kore just double-click on Kore-compile.bat.
Kore第二讲: Kore与Perl


Kore第二讲: Kore与Perl
Kore第二讲: Kore与Perl by ICE-WR


Kore是用perl语言来编写的,perl是一种简单而强大的脚本语言,.pl的文件就是perl脚本,需要解释器来运行,例如:Windows平台下的Active Perl。

要想了解Kore,不需要对perl非常精通,当你第一次看Kore.pl时估计会非常头痛,一些语句甚至无法理解,大部分都是一些匹配模式,例如:

foreach () {
next if (/^#/);
s/[\r\n]//g;
s/\s+/ /g;
s/\s+$//g;
@args = split /\s/, $_;
}

建议你可以先不看这些语句,下面附带Perl的教学文件:

http://www.mu20.com/viewFile.asp?Boardid=14&ID=1281

编写.pl文件的话,严重推荐用这个软件:UltraEditor 32


Kore第三讲: Kore的程序结构及主程序解释


Kore第三讲: Kore的程序结构及主程序解释
Kore第三讲: Kore的程序结构及主程序解释 by ICE-WR

Kore的程序执行流程如下:


1)程序初始化:调用模块,初始化变量
2)读入数据文件:包括config文件夹和tables文件夹
3)建立控制台指令输入连接及与服务器连接
4)执行主程序,主程序是个循环,只要没有接收到quit指令就一直执行,包括如下功能:
a)执行控制台指令
b)分析接收到的封包,转化为Kore里的游戏信息,如HP/SP等
c)执行AI:AI决定人物角色需要做什么,发送什么指令到服务器
d)检查连接情况
5)接收到quit指令后结束连接和程序

Kore原版有8000行代码,但实际上主程序只有20行代码!了解主程序你就能知道整个运作原理了,以下位主程序代码的解释:

while ($quit != 1) {
#当不是$quit = 1时,一直执行以下程序,假如你在Kore里输入quit,那就会退出Kore啦
usleep($config{'sleepTime'});
#usleep就是要睡眠多少微秒,作用是等待封包接收及减低CPU占用率

if (dataWaiting(\$无效_socket)) {
#$无效_socket用来读取控制台指令,如果有指令输入,则执行以下代码
$无效_socket->recv($无效, $MAX_READ);
#从$无效_socket读取数据并储存在$无效,例如我们在Kore里输入“i\n”,那$无效就等于“i\n”,“\n”代表回车
parse无效($无效);
#parse无效是个子程序,用来执行控制台指令

} elsif (dataWaiting(\$remote_socket)) {
#$remote_socket用来接收封包,假如服务器发送了封包过来,则执行以下代码
$remote_socket->recv($new, $MAX_READ);
#从$remote_socket读取服务器发送过来的数据,$MAX_READ是用来限制一次读入的数据量
$msg .= $new;
#由于服务器每次发送的封包不一定是完整的数据,所以Kore会把封包连接起来并储存在$msg
$msg_length = length($msg);
#这里是计算解释封包前的长度
while ($msg ne "" {
#当有封包数据时,一直执行下面的代码
$msg = parseMsg($msg);
#parseMsg是封包解释子程序,负责把封包转换为各种数据,例如HP/SP等,运行时读取$msg,运行后把已经解释完毕的封包去掉,并返回给$msg,这个过程就是,读完一段就删除一段
last if ($msg_length == length($msg));
#因为封包解释子程序是解释完一段就删除一段的,所以这里比较解释前和解释后的$msg长度,来判断是否仍然有未解释的封包,假如解释前和解释后的$msg长度一样,那就代表已经没有要解释的封包,终止执行这个封包解释循环
$msg_length = length($msg);
#记录下解释后的封包长度,用作下一次比较
}
}
#上面的循环是用作接收指令和解释封包,完成后会运行以下子程序“AI”
$ai_cmdQue_shift = 0;
#$ai_cmdQue是用作远程控制(聊天指令控制)
do {
AI(\%{$ai_cmdQue[$ai_cmdQue_shift]}) if ($conState == 5 && timeOut(\%{$timeout{'ai'}}) && $remote_socket && $remote_socket->connected());
#$conState 5代表已经完成登陆过程,并开始游戏;因为进入游戏后会收到大量信息,所以等待timeout.txt里AI指定的秒数后才执行AI;后面两个判断是代表连接正常
undef %{$ai_cmdQue[$ai_cmdQue_shift++]};
$ai_cmdQue-- if ($ai_cmdQue > 0);
#以上两句是用作执行远程控制
} while ($ai_cmdQue > 0);

checkConnection();
#管理数据通讯连接状况的子程序,例如:登陆和断线等功能
}
Kore第四讲: RO封包简要说明


Kore第四讲: RO封包简要说明
Kore第四讲: RO封包简要说明 by ICE-WR

RO客户端与服务器间通过TCP协议连接,并互相传递信息,封包内容类似



B0 00 05 00 E0 10 00 00
#00b0 w l

RO的封包头包含2个字节,数值从0064到01FF,上面的封包头为00B0,每种封包分别代表不同类型的信息。

RO封包有2种,一种是固定长度,一种是非固定长度,类型和各封包长度在ragexe.exe里记录,KARASU把RAGEXE.EXE破解后翻译出全部封包长度。

例如00B0是固定长度8字节,但如果一个封包并不是固定长度,那服务器需要告知RO客户端封包的长度,对非固定长度的封包,在封包的第三和第四个字节用来表示封包长度

把类似“B0 00 05 00 E0 10 00 00”这种16进制形式的代码转换为数值,在perl里通常用substr和unpack函数从封包里转换为具体数值(perl函数请参考Kore第二讲里的perl教学文件)

例如Kore的封包分析子程序里对B0 00 05 00 E0 10 00 00的解释执行如下:

} elsif ($switch eq "00B0" {
#$switch在Kore里代表封包头,当封包头为00B0时执行下面代码

$无效 = unpack("S1",substr($msg, 2, 2));
#这里代表截取封包里05 00并翻译为无符号短整数,赋值给$无效,这时$无效 = 5
$val = unpack("L1",substr($msg, 4, 4));
#截取E0 10 00 00并翻译为无符号长整数,赋值给$val,这时$val = 4320

if ($无效 == 0) {
print "Something1: $val\n" if $config{'debug'};
} elsif ($无效 == 3) {
print "Something2: $val\n" if $config{'debug'};
} elsif ($无效 == 5) {
#当$无效等于5时执行
$chars[$config{'char'}]{'hp'} = $val;
#把$val赋值给$chars[$config{'char'}]{'hp'}
print "Hp: $val\n" if ($config{'debug'} >= 2);
} ...

以上的封包分析后,这个封包的内容就是告知客户端当前人物HP = 4320

Kore第五讲: 封包分析子程序运作详解


Kore第五讲: 封包分析子程序运作详解
Kore第五讲: 封包分析子程序运作详解 by ICE-WR


从前面的介绍我们知道当收到RO服务器封包时,会执行子程序parseMsg()来进行封包分析,那parseMsg()是怎么运作的呢?



首先从Kore主程序结构里得知,Kore采用阻塞的方式进行封包接收,就是每隔一段时间收一次,这个循环时间大概是AI执行时间加上config.txt里sleepTime的微妙数。收回来的封包会放在$msg里,然后把$msg交给parseMsg()来进行分析,parseMsg()的执行过程如下:

1)取$msg前两个字节,解释为封包头
2)根据封包头来选择分析代码
3)把分析了的封包信息删除,并把还没分析的返回给主程序

主程序根据$msg在执行parseMsg()分析前和分析后的长度是否相等,来判断是否还需要进行分析,假如分析前与分析后长度一样,代表分析完毕,假如不等,则继续循环(请参照第三节内容)

目前封包信息已知头信息均为0064~01FF,另外还有一个种只包含ACCOUNT ID,以下为KE内的分析代码,采用MODKORE的计算方式

在主程序阶段执行了$msg = parseMsg($msg);后,运行以下代码

sub parseMsg {
my $msg = shift;
#my为定义局部变量,shift为传递第一个子程序参数,并删除参数,perl里的子程序参数都以数组形式传递,这里是把主程序里的$msg引用到子程序中
my $msg_size;
#定义局部变量$msg_size,看字面解释就很清楚:“封包长度”

if (length($msg) < 2) {
#length()函数是用来计算字符串长度,假如封包长度小于2,那就执行下面语句
return $msg;
#结束子程序,并返回$msg给主程序
}
#以上代码表示,最短可分析的封包长度是2,假如长度小于2,那就不分析

$switch = uc(unpack("H2", substr($msg, 1, 1))) . uc(unpack("H2", substr($msg, 0, 1)));
#这里把封包前两位截取出来并赋值给$switch,$switch在子程序内代表封包头

if (length($msg) >= 4 && substr($msg,0,4) ne $accountID && $conState >= 4 && $lastswitch ne $switch && length($msg) >= unpack("S1", substr($msg, 0, 2))) {
decrypt(\$msg, $msg);
}
#这里是执行封包解密,有些服务器是封包加密形式的,对CRO没用

$switch = uc(unpack("H2", substr($msg, 1, 1))) . uc(unpack("H2", substr($msg, 0, 1)));
#封包解密后从新计算封包头,对CRO没用

if ($lastswitch eq $switch && length($msg) > $lastMsgLength) {
$errorCount++;
} else {
$errorCount = 0;
}
if ($errorCount > 3) {
dumpData($msg);
$errorCount = 0;
$msg_size = length($msg);
print "封包解释错误: $last_know_switch > $switch ($msg_size)\n";
}
#以上是效验封包完整性,假如一个封包经过三次接收都还没被分析,会被认为是封包错误,把已经接收到的封包全部去掉。造成封包错误的主要原因是封包长度错误,例如00B0长度8,假如我设置了00B0为长度6,经过一次封包解释后,删除了前面6个字节,得到后面的封包头为00 00,不在0064~01FF这个范围,那就不能进行分析,Kore必须丢弃这些这些封包才能确保封包分析能继续执行下去。

$lastswitch = $switch;
#计算前记录下上一次分析前的封包头(用作完整性判断)

if (substr($msg,0,4) ne $accountID || ($conState != 2 && $conState != 4)) {
#RO封包里有个特殊的封包只返回Account ID,假如不是这个封包则进行下面的封包长度计算,$rpackets{$switch}代表从rpackets.txt里读取出来的封包长度,例如$rpackets{'00B0'} = 8。$switch范围为0064~01FF,超出这个范围的封包头肯定代表封包接收有问题!
if ($rpackets{$switch} eq "-" {
#这种封包会把整个封包截取出来分析(实际上RO并没有这这种封包,只是在KARASU未解析出全部封包长度前,所遗留下来的问题- -b)
$msg_size = length($msg);
} elsif ($rpackets{$switch} eq "0" {
#判断是否非固定长度类封包,封包头为前2字节,封包长度保存在封包的第3-4字节里
if (length($msg) < 4) {
return $msg;
}
#由于这种封包前4位分别记录封包头和封包长度,所以小于4字节时代表封包还没接收完整,因此返回到主程序
$msg_size = unpack("S1", substr($msg, 2, 2));
#这里是从第3~4字节里计算出封包长度
if (length($msg) < $msg_size) {
return $msg;
}
#假如收到的$msg还没到这个长度,代表封包还没接收完整,返回到主程序
} elsif ($rpackets{$switch} > 1) {
#判断是否固定长度封包
if (length($msg) < $rpackets{$switch}) {
return $msg;
}
#假如接收到的封包小于该封包的长度,代表封包还没接收完整,返回主程序
$msg_size = $rpackets{$switch};
#计算封包长度
} else {
dumpData($last_know_msg.$msg);
#假如封包头不是在0064~01FF之间,代表封包接收或分析有问题,dumpDate会记录下目前已经收到的封包,并记录在dump.txt里
}
$last_know_msg = substr($msg, 0, $msg_size);
$last_know_switch = $switch;
#以上用作效验封包完整性
}

$lastMsgLength = length($msg);
#以上用作计算封包完整性


接着会是一大堆if ($switch eq "xxxx"... elsif ... elsif ... elsif,这些就是具体到每种封包的分析...

完成分析后,在parseMsg()的最后会从$msg删除已经分析过的封包,如下:

$msg = (length($msg) >= $msg_size) ? substr($msg, $msg_size, length($msg) - $msg_size) : "";
return $msg;

--- luckyboa

扑(16):多种Kore代码下载


2004年8月3日更新,以下为压缩包含:

Kore-0.92.81
作者:Kura
简介:全部Kore类外挂的祖先,停止开发时已经具备了大部分功能

X-Kore-0.58.02
作者:Kura
简介:RO内挂的祖先,几乎具备了Kore全部的功能,可以执行Kore的脚本

openkore-win32-1.2.0
作者:openKore Team
简介:openKore持续发展,第一个Kore与X-Kore相结合,并把Kore的程序标准化

modKoer-Lite
作者:未知
简介:国外非常出名的Kore,有着自己特殊的写作风格,代码参照早期的SKore和openKore

Kore-XP-1.0405.2004
作者:modKore的作者
简介:最好的图形化Kore,modKore的延续

zKore-clio-snapshot
作者:Karasu
简介:台湾Kore的技术支柱!与日本jKore有很大关联,KoreC大量参考zKore核心。

KoreEasy-0.7.99t
作者:ICE-WR
简介:以简单和挂机为主要开发理念,因为一直没作为开放代码而写,所以比较乱

KoreMVP-0.5.51
作者:ICE-WR
简介:第一个专门针对打MVP的加速内挂

--- luckyboa

猫(17):JAVA的KERO代码:


public void run (){
buf = ByteBuffer.allocate(RobotConst.MAX_LENGTH);
while ( true ) {
try {

if ( (socket != null && socket.isConnected() == true) && (msgLength = socket.read(buf)) != 0 ) {

System.out.println("A";

if (msgLength != 0){
getMessage = new byte [msgLength];
buf.flip();
buf.get(getMessage);
System.out.println(msgLength);

recvMessage = MsgDeal.filterByte(getMessage ,msgLength);
System.out.println("C";
if ( lastMessage != null ){
recvMessage = MsgDeal.byteArrayCat(lastMessage ,recvMessage);
System.out.println("D";
} //if end
while ( true ){
System.out.println(MsgDeal.getMsgHead( recvMessage , 0 , 2 ));
dataLength = MsgDeal.getMsgLength(MsgDeal.getMsgHead( recvMessage , 0 , 2 ));
System.out.println("dataLength:"+dataLength);

if ( dataLength == 0 ){
//translate the length of unfixed message
dataLength = Integer.parseInt(
MsgDeal.toHexMsg(MsgDeal.getInfoFromMsg( recvMessage , 2 , 2 )) , 16);
System.out.println("true dataLength:"+dataLength);
} else if( dataLength == -1 ){
recvMessage = null ;
break ;
}//if end
System.out.println(recvMessage.length);
//if date is not integrity ,then break
if ( dataLength > recvMessage.length ){
System.out.println("break";
lastMessage = recvMessage ;
break ;
} // if end
fullMessage = MsgDeal.getInfoFromMsg( recvMessage , 0 , dataLength );
System.out.println(MsgDeal.displayMsg(fullMessage));
ParseMsg.translateTcpData(fullMessage);
System.out.println("3";
//get the left meesage
recvMessage = MsgDeal.getInfoFromMsg( recvMessage , dataLength , recvMessage.length - dataLength);
System.out.println("4");
if ( recvMessage == null ){
lastMessage = null ;
System.out.println("5");
break ;
} // if end
lastMessage = recvMessage ;
System.out.println("6");
} //while end
} // if end
buf = ByteBuffer.allocate(RobotConst.MAX_LENGTH);
} // if end

} catch (ClosedChannelException e) {
System.out.println(e.toString());
lastMessage = null ;
e.printStackTrace();
} catch (IOException e) {

}

try{
if ( (socket != null && socket.isConnected() == true)
&& RoBot.srvInfo.getSendMsgList().isEmpty() != true){
if ( RoBot.srvInfo.getConState() == RobotConst.LoginSuccess ){
if(false){
sendMessage = MsgDeal.getTickCount();
}
}
sendMessage = (byte[])RoBot.srvInfo.getSendMsgList().get(0);
System.out.println(MsgDeal.displayMsg(sendMessage));
ByteBuffer bb = ByteBuffer.wrap(sendMessage);

socket.write(bb);
RoBot.srvInfo.getSendMsgList().remove(0);
}//if end
}catch (IOException e){
e.printStackTrace();
sendMessage = null;
}

try {
checkConnection();
} catch (IOException e1) {
e1.printStackTrace();
}

checkTimeOut();

try {
Thread.sleep(50);
} catch (InterruptedException e2) {
e2.printStackTrace();
}

} // while end
} //class end

/**
* server socket
*/
private SocketChannel socket ;

/**
* ip address
*/
private InetSocketAddress inetSocketAddress ;

/**
* buffered the byteArray
*/
private ByteBuffer buf ;

/**
* the message what will send to server
*/
private byte[] sendMessage ;

/**
* the meesage get from the socket in a tcp package whick has be filtered
*/
private byte[] recvMessage ;

/**
* the message which has not parsed is set in it
*/
private byte[] lastMessage ;

/**
* the package is all data in it
*/
private byte[] fullMessage ;

/**
* the meesage get from the socket in a tcp package
*/
private byte[] getMessage ;

/**
* the length of a tcp package
*/
private int msgLength ;

/**
* a data package's true length
*/
private int dataLength ;

我在最近编译一些perl的脚本时候发现,perlapp的确很少有人说的比较详细的使用方法
基本最多就是perlapp一些参数介绍没有太多实际用途,百度,google都没有找到太多有价值的,只能自己琢磨了。
现在稍微悟到了一些,希望能和大家共享
首先来谈一个问题,perlapp 编译的那个脚本,对于模块,如果是标准模块,自然不需要太多顾虑,但是如果有些自己定义的模块,那就首先需要在自己的脚本上加
unshift @INC,"e:\somedir";
然后大胆的使用
如果你不放心自己的@INC
可以打印出来看看print join("\n",@INC);
use xxx;(尽可能多的把你自己定义的模块全部use进来)
require "xxx.pl";
代码若干。。。。。。。
然后开始编译
perlapp --version start.pl --lib=e:\somedir\src --bind=e:\somedir\xxx.dll --bind=e:\somedir\dir2\xxx.pl 可以绑定很多的,
然后就是有慢慢等待成果了,hoho!