红心大战c语言程序设计教程课后答案,[原创]Windows 红心大战随机发牌程序分析...

[调试逆向]

[原创]Windows 红心大战随机发牌程序分析

2007-11-27 23:28

13191

[调试逆向]

[原创]Windows 红心大战随机发牌程序分析

2007-11-27 23:28

13191

Windows 红心大战有52张牌,每次开局时,程序会自动洗牌和发牌。

按照我们一般的想法,随机发牌无非就是随机产生0..51个随机数,代表52张牌。然后分发给四个玩家。当随机产生0...51之间的随机数时,可能会产生相同的随机数,此时,就要将这种情况去除,并重新产生一个随机数,直到完全产生0...51之间所有的数值,这种方式下,调用随机函数的次数是不固定的,而且肯定大于52次。

研究了一下红心大战,发现这个程序并不是这样洗牌的,它的洗牌方式有点巧妙,只需调用52次随机函数,就可以完全随机地整理出一幅牌来。这里面相关的代码并不多,只有四十来行,却花了我一个晚上,才看明白其中的道理,高兴之余,赶快拿出来与我一样的菜鸟分享。失误之处,请高手指正。

在OD中跟踪红心大战,很容易跟踪到随机发牌的地方,这里就不多说了。确定了函数地址后,再用IDA定位到这一段程序,如下:

.text:01007F89 private: void __thiscall CMainWindow::Shuffle(void) proc near

.text:01007F89                                         ; CODE XREF: OnNewGame+10Fp

.text:01007F89                                         ; CMainWindow::DispatchCards(void):loc_1008966p

.text:01007F89                 mov     eax, offset unknown_libname_40 ; MFC4.2 release library by ForEver[RCT]

.text:01007F89                                         ; __ehhandler$?ExecCommand@?$CHtmlEditCtrlBase@VCHtmlEditView@@@@QBEJPBU_GUID@@JJPAUtagVARIANT@@1@Z

.text:01007F8E                 call    __EH_prolog

.text:01007F8E

.text:01007F93                 sub     esp, 104h

.text:01007F99                 push    ebx

.text:01007F9A                 push    esi

.text:01007F9B                 push    edi

.text:01007F9C                 push    34h

.text:01007F9E                 mov     esi, ecx

.text:01007FA0                 xor     eax, eax

.text:01007FA2                 pop     ecx             ; ecx=34h (52张牌)

.text:01007FA2

.text:01007FA3 在内存中产生一幅牌

.text:01007FA3

.text:01007FA3 loc_1007FA3:                            ; CODE XREF: CMainWindow::Shuffle(void)+24j

.text:01007FA3                 mov     [ebp+eax*4-110h], eax

.text:01007FAA                 inc     eax

.text:01007FAB                 cmp     eax, ecx

.text:01007FAD                 jl      short loc_1007FA3

.text:01007FAD

.text:01007FAF                 and     dword ptr [ebp-14h], 0                 ; [ebp-14h] 已发牌数,置初值0

.text:01007FB3                 mov     [ebp-18h], ecx                          ; [ebp-18h]:末发牌数,置初值:52

.text:01007FB3

.text:01007FB6 发牌

.text:01007FB6

.text:01007FB6 locNextCard:                            ; CODE XREF: CMainWindow::Shuffle(void)+8Aj

.text:01007FB6                 call    ds:__imp__rand

.text:01007FBC                 cdq

.text:01007FBD                 idiv    dword ptr [ebp-18h]                 ; 随机数/末发牌数 (余数edx 就是要抽取的牌的位置)

.text:01007FC0                 mov     eax, [ebp-14h]                          ; eax=当前已发牌数

.text:01007FC3                 push    0Dh

.text:01007FC5                 pop     edi                                     ; edi=13

.text:01007FC6                 push    4

.text:01007FC8                 pop     ebx

.text:01007FC9                 mov     ecx, edx                                ; 要抽取的牌的位置

.text:01007FCB                 cdq

.text:01007FCC                 idiv    edi                                     ; edi=13, eax就只能是0,1,2,3, edx只能是:0...12

.text:01007FCE                 sub     eax, [esi+140h]                         ; [esi+140h]=0, eax得到当前是给哪一位发牌,只可能是0,1,2,3

.text:01007FD4                 mov     edi, edx                                ; edi只能是0...12

.text:01007FD6                 add     eax, 4                                  ; eax只能是4,5,6,7

.text:01007FD9                 cdq

.text:01007FDA                 idiv    ebx                                     ; ebx=4, edx就只能为0,1,2,3 分别代表四个玩家

.text:01007FDC                 lea     eax, [ebp+ecx*4-110h]

.text:01007FE3                 mov     ebx, [eax]                              ; ebx=[ebp+ecx*4-110h] 这里面就是内存中的那副牌

.text:01007FE3                                                                 ; 抽出这张牌,传给ebx

.text:01007FE5                 shl     edi, 4                                ; 玩家的当前牌和第一张牌偏移地址(相邻两张牌的地址相隔10h)

.text:01007FE8                 lea     ecx, [esi+edx*4+130h]                ; 要发往玩家牌当前牌的地址

.text:01007FEF                 mov     edx, [ecx]

.text:01007FF1                 mov     [edi+edx+1Ch], ebx                 ; 发牌给某一个玩家

.text:01007FF5                 mov     ecx, [ecx]

.text:01007FF7                 xor     ebx, ebx

.text:01007FF9                 dec     dword ptr [ebp-18h]                 ; 统计末发牌数

.text:01007FFC                 inc     dword ptr [ebp-14h]                 ; 统计已发牌数

.text:01007FFF                 cmp     dword ptr [ebp-14h], 34h                         ; 已发牌数达到52张?

.text:01008003                 mov     [edi+ecx+28h], ebx

.text:01008007                 mov     ecx, [ebp-18h]

.text:0100800A                 mov     ecx, [ebp+ecx*4-110h]

.text:01008011                 mov     [eax], ecx                              ; 将最后一张牌插入当前抽出牌的位置上来

.text:01008013                 jl      short locNextCard

.text:01008013

.text:01008015                 cmp     dword ptr [esi+144h], 3

.text:0100801C                 jz      short loc_100806A

--------------------------------------------------------------------------------------------------------------------------------

程序分析说明:

程序先在内存中按照: 梅A 方A 红A 黑A  梅2 方2 红2 黑2 ... 梅K 方K 红K 黑K 的顺序有规率地产生一幅牌。

内存数据如下:

0006F9E4                                        00 00 00 00  燣/.`K/.........

0006F9F4  01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00  ............

0006FA04  05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00  ............

0006FA14  09 00 00 00 0A 00 00 00 0B 00 00 00 0C 00 00 00  ...............

0006FA24  0D 00 00 00 0E 00 00 00 0F 00 00 00 10 00 00 00  .............

0006FA34  11 00 00 00 12 00 00 00 13 00 00 00 14 00 00 00  ............

0006FA44  15 00 00 00 16 00 00 00 17 00 00 00 18 00 00 00  ............

0006FA54  19 00 00 00 1A 00 00 00 1B 00 00 00 1C 00 00 00  ............

0006FA64  1D 00 00 00 1E 00 00 00 1F 00 00 00 20 00 00 00  ......... ...

0006FA74  21 00 00 00 22 00 00 00 23 00 00 00 24 00 00 00  !..."...#...$...

0006FA84  25 00 00 00 26 00 00 00 27 00 00 00 28 00 00 00  3...&...'...(...

0006FA94  29 00 00 00 2A 00 00 00 2B 00 00 00 2C 00 00 00  )...*...+...,...

0006FAA4  2D 00 00 00 2E 00 00 00 2F 00 00 00 30 00 00 00  -......./...0...

0006FAB4  31 00 00 00 32 00 00 00 33 00 00 00

程序将牌按顺序整理好后,然后随机抽牌,分发给四个玩家。

发牌的顺序是先给第一个玩家发13张牌,然后给第二个玩家发13张牌,再给第三个玩家发13张牌,最后给第四个玩家发13张牌。

程序中设置一个变量,记住末发牌数,假设为n,每次发牌时,就是在 0...(n-1) 的范围内产生一个随机数,这个随机数就是内存中那幅牌中的第几张牌,然后将这张牌抽出,发给玩家后,再将余下牌中的最后一张牌(索引号:n-1)放到这张牌的位置上来。

这样,产生的随机数虽然可能相同,那只是表示要抽取的牌的位置是相同的,由于牌已经被更换,所以每次抽取的牌肯定是不一样的。

这种方法的确很巧妙!对于其它的相关编程也有触类旁通的效果。自我感觉从这里受益不少!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值