【已更新】《WinXP空当接龙》加入无限撤销和存档功能

查看 100|回复 9
作者:klise   
【前言】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[color=]首先说明:该版本在 WinXP/Win7/Win10 都能玩,绿色软件,不用辛辛苦苦去找XP电脑,也不用辛辛苦苦装一套虚拟机。
[color=]另外,网友反映的游戏bug已经修复,详见【游戏下载】的说明。
这是小弟第一次在“脱壳破解区”发帖,如有不妥之处,或者无意中违反了规定,务请及时指出,我立即改正。
我一直喜欢玩《空当接龙》,特别是对 WinXP 自带的版本情有独钟,相信这个版本也是很多朋友的童年回忆。
不过,WinXP的《空当接龙》有个很麻烦的问题,就是只能撤销一次,有时候一步不慎,就要从头开始。
尽管 Win7 以后的《空当接龙》已经可以无限撤销,但是我希望自己动手,给《WinXP空当接龙》加上“无限撤销”的功能,一来算是自己练练手,二来我还是喜欢 WinXP 这个版本,也是给自己一种便利。
网上也有很多《空当接龙》的升级版,基本上都是用高级语言(源代码)重新编写的,比如VC、VB、Delphi,属于全新开发。“全新开发”的好处是,可以任意增加功能,不受原版exe的局限。不过,这与“破解逆向”的关系不大。
我决定用WinXP原版《空当接龙》进行改进,加入无限撤销、存档读档功能。改进后的《空当接龙》加强版取名《JF接龙》,JF是 Just Fun 的意思,所谓“世事无绝对,只有真情趣”,也就是 No Worry, Just Fun ...
今年(2020年)即将完结,这一年大家见证了太多历史,这里顺祝各位朋友在新的一年里 No Worry, Just Fun ...
以下是我开发《JF接龙》的过程记述,由于开发的步骤互相穿插,所以次序上并非严格的顺序,想到哪写到哪。
【增加区段 —— 开垦自留地】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
“增加区段”这个操作,通常大家都用不上,这是因为常规的破解(爆破)需要的代码量并不大,有时候把  je 改成 jmp 就可以了 :)
但是,这次的任务与通常的“破解”不同,这次并不是单纯的破解,而是要给《空当接龙》加入新的功能。这就必然要加入额外的代码,说不定还要储存一些数据什么的。
可以想象,原版exe是 不会 给我们预留多余的空间的,因此最好的办法是给exe增加一个区段,作为自用的自留地”,把新增的代码、数据放在新区段里。
至于加入的区段要多大,这个一开始只是一种估算,不过以后可以调整,不够再加嘛,所以先加个 4KB 的区段,边走边看吧。
【诡异的 freecell.exe 】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
WinXP 的《空当接龙》执行文件位于  Windows\system32 目录中,由 freecell.exe 和 cards.dll 构成:
    freecell.exe     54KB     主程序,负责开局、移牌、判断胜负等
    cards.dll        352KB    资源文件,包含52张纸牌的图像、绘制等
这个 freecell.exe 就是我们动手的目标,它只有 54KB,没有加壳(微软官方的东西,是不会加壳的)。
加入区段的工具很多,我用 Stud_PE (用国内的 StudyPE 也可以)。


01.png (27.42 KB, 下载次数: 0)
下载附件
2020-11-26 10:51 上传

我曾经给 N个 exe 加过区段(比如《盟军敢死队》系列),不过这次却被这个只有54K的小家伙难倒了。加了区段之后,竟然无法运行?


01.png (13.59 KB, 下载次数: 0)
下载附件
2020-11-26 10:55 上传

用 OD 打开一看,里面的 API 函数全乱了,疑似是 IAT 的问题?


error.png (54.56 KB, 下载次数: 0)
下载附件
2020-11-26 10:58 上传

这个问题困扰了我几天,我用过几种流行工具,包括 pe-explorer, ImportRE 等,都无法解决,还在『脱壳破解讨论求助区』发帖请教。
后来我忽然想到,还有 LordPE 没试过,死马当作活马医,点击【重建PE】,显示“存在重定位”,清除成功。


01.png (20.63 KB, 下载次数: 0)
下载附件
2020-11-26 11:03 上传

然后双击重建后的 exe,顺利运行了!
搞了半天,原来这是传说中的“exe 重定位”!
我们一直都知道,DLL 是有重定位的,至于exe,理论上也有重定位,不过我们真的从来没有遇到过,没想到在这个只有54KB的小家伙身上遇到了!
这次真是开眼了,2020年,真是见证历史呀!
这次最终用 LordPE 搞定了,看来关键时候,还是要靠老前辈啊!
【寻找撤销功能】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在LordPE重建exe后,就顺利加入新区段了,自留地开垦成功了。
接下来开始“无限撤销”的功能。关于《空当接龙》的内部情况,看雪的前辈有一篇文章进行了详细说明,让我节省了不少时间,少走了很多弯路。在此向所有前辈致敬!
[原创]空当接龙逆向算法分析
这篇文章没有提到“撤销”功能,但是从中可知,牌局中的52张纸牌存放在 0x01007500 数据区,那么所有纸牌的变动(移动、撤销),必然在这里反映出来。
这里先说明“中转站”和“终点站”的概念,如图,不必赘述。


aa.png (24.7 KB, 下载次数: 0)
下载附件
2020-11-26 11:31 上传

我们选一局 #11 ,把 梅花A 弹上去,同时对 0x01007500 数据区 进行截图对比。


01.png (27.35 KB, 下载次数: 0)
下载附件
2020-11-26 11:24 上传

我们知道, 在 0x01007500 数据区中,“开头”就是中转站、终点站的数据,而“开头”的数据是最直观、最容易观察的。
我们只要“故意”把纸牌打上中转站、终点站,就能看到数据变化。
下面把 梅花A 弹上去的前、后数据进行截图对比:


aa.png (34.76 KB, 下载次数: 0)
下载附件
2020-11-26 11:39 上传

我们看到,0x01007510 单元的数据从 -1 变成 0 了!
这就简单了,对 0x01007510 下“硬件写入”断点,然后按一下 F10 撤销,梅花A 回退,它又从 0 变回 -1,并且被 OD 断下来了。


aa.png (38.24 KB, 下载次数: 0)
下载附件
2020-11-26 11:48 上传

仔细观察,发现两个情况:
1、 这是一个循环过程。这个容易理解,回退嘛,有可能是几张牌一起回去,所以是个循环执行
2、 梅花A 并不是直接回去的,而是先跑到中转站,然后再回去原位


aa.png (31.43 KB, 下载次数: 0)
下载附件
2020-11-26 11:55 上传

这个过程就有意思了,一张牌也分成几步走?
既然是一个循环,往上追溯,看到几个很有意思的地方。对了,现在不用硬件断点了,直接 F2 下断点就行了。


aa.png (38.41 KB, 下载次数: 0)
下载附件
2020-11-26 12:03 上传

上图有4个要点,这是仔细分析后得到的,直接说结论:
1、 地址 [0x1007984] 是回退的步数,比如 梅花A 是 2步,先到中转站,再回到原位
2、 取得 edi= [0x1007984] 之后,乘以16,也就是步数*16,每一步占用16个字节
3、 加上固定地址 0x010079CC,可见 0x010079CC 应该是起始地址,但是 ……
4、 但是,下面还要 [edi-0x4]、 [edi-0xC]、 [edi-0x8],显然数据区还要往前推,
留意到 [edi-0xC] 是最前方,也就是说, 0x010079CC  减去 0xC 是最开端的地址,也就是  0x010079C0
捋一下这个循环过程,大意就是:
1、取 [0x1007984] 的回退步数,比如 步数=5
2、每一步占用16字节
3、从 0x010079C0 开始,依次存放着每一步的16字节,依次读取5遍,然后解释执行
[color=]至此,我们找到了“回退步骤”最重要的数据区,也就是数据源,接下来的工作相对简单了,我就不用写得太啰嗦了。
[color=]接下来以步骤、思路为主,至于实现方法,大家可以各自发挥,不必局限于我的做法。
【监测“回退数据”】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
原版exe只能保留一次回撤,你点击一下鼠标,这个回退数据就会消失了。
为了实现“无限撤销”功能,我们自然会想到,需要把原版exe每次生成的“回退数据”截留下来。如果没有这些数据,就谈不上“无限撤销”了。
既然知道了回退数据保存在 [0x1007984] 和 0x010079C0 ,只要下“硬件写入”断点,就能监测到什么时候写入(生成)回退数据。
这是因为,既然有回退,那么步数一定大于0,一定要写入 [0x1007984] 地址。
通过检测  [0x1007984] 地址,找到了代码所在,原始就在纸牌发生移动之后, 紧接着就写入回退步数。


aa.png (34.56 KB, 下载次数: 1)
下载附件
2020-11-26 12:37 上传

【截留“回退数据”】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
紧接上一步,既然找到了“回退数据”生成的源头,就要赶紧把这些数据拷贝出来,另外找地方保存。
否则玩家随便点一下鼠标,这个回退数据就会消失了。
保存的数据很简单,地址从 0x010079C0 开始,长度(字节数)就是的步数乘以16。
熟悉汇编的朋友一下就想到了,无非就是:
[Asm] 纯文本查看 复制代码
-
mov esi, 0x010079C0
mov edi, 我的自留地
mov ecx,[0x1007984]
shl ecx,4
rep movSB
-
那么保存到哪里呢?(啥?上传到百度网盘?……)
这就要用到另外开辟的内存了,有几种做法:
1、标准方法是申请一块内存,所有数据全部放进去
2、我们把新加的区段搞大一些,比如放个100KB,直接往里面写数据
具体到 freecell.exe 而言, 第1种方法不是那么方便,因为exe里面没有引入关于内存的API,你要自己找 malloc 或者 calloc 的接口( freecell 里面有 GetProcAddress,因为它自己要调入 cards.dll ),还要申请内存、释放内存,看着有点头大。
因此建议用第2种方法,简单直接,不过,切记用 LordPE 重建一下,然后加区段,切记!
比如用 stud_pe 加入区段,注意“实际大小”和“虚拟大小”必须一致,然后“用零填充区段”,这样就行了。这里 0x8000 就是 32KB 空间。


aa.png (30.32 KB, 下载次数: 0)
下载附件
2020-11-26 13:51 上传

【“回退”调度】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
我们设想一下,比如用户玩了5步,我们储存了5次回退数据,称为 D1、D2……D5,来看两种情况:
1、用户按一下 F10,应该回退 D5。如果用户继续按F10,那么继续回退 D4。
2、用户按一下 F10,应该回退 D5。这时用户继续玩下一步,那么就产生新的回退数据,截留出来保存在D5(之前的数据已经回退,作废了)
这个过程就是“后进先出”,类似于堆栈的原理。
因此,在保存“回退数据”时,要设计一个“调度”方式,而这个调度方式又直接影响到你复制(拷贝)数据的做法。
说到这里,已经不是“破解技巧”的问题,而是思路、方法的问题了。
【修改快捷键】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
原版exe通过F10进行撤销,通常我们用右手操作鼠标,左手按快捷键,那么左手按F10不太方便,如果换成F1,那就方便多了。
修改快捷键,最简单的方法是修改exe的“资源”。用 Resource Hacker 打开 exe,看到“快捷键”的定义。
容易理解,VK_F1 就是指 F1 键,那么 106 是什么呢?由于 windows 采用“消息机制”,这个 106 是“窗体消息”编号,你按下F1,游戏窗口就接收到 “106号消息”。
类似的,VK_F10 的消息编号是 115,先拿出小本子,记下来。


aa.png (53.01 KB, 下载次数: 0)
下载附件
2020-11-26 14:17 上传

我把 F1 和 F10 等同起来,用户按下 F1 或者 F10 都可以“无限撤销”,这样就兼顾了 老用户 的习惯。
如下图,VK_F1 和 VK_F10 都设置为 1024,顺便把两个 Shift 组合键删除,避免发生干扰。


aa.png (51.41 KB, 下载次数: 0)
下载附件
2020-11-26 14:27 上传

如上图,F1 和 F10 都统一为“1024消息”,用户按下这两个键,都是产生相同的消息,执行相同的动作。
为什么是 1024 呢? 这是因为 1024 以下是Windows系统自带的消息,比如滚动条、按钮之类,而 1024 以上就是给你编程使用的,比如 1997、2046 这些都是。
【拦截窗体(快捷键)消息】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
现在要处理 F1/F10 了,也就是“窗体消息1024”。这就必须找到游戏窗口的消息处理过程。
找消息过程,最简单的办法是拦截 DefWindowProcA 或者 DefWindowProcW,因为处理完消息之后,一定会执行一次 DefWindowProc 过程 。
在 OD 中,右键->查找->所有模块中的名称:



a1.png (51.14 KB, 下载次数: 0)
下载附件
2020-11-26 14:56 上传

然后,按照“区段”排列,在 text 区段找到 USER32.DefWindowProcW,看到地址是 0x01001124 :


a2.png (45.56 KB, 下载次数: 0)
下载附件
2020-11-26 14:57 上传

在OD的数据区,转到 0x01001124 地址,右键->查找参考:


aa.png (50.92 KB, 下载次数: 0)
下载附件
2020-11-26 15:03 上传

就看到执行的地址了,下去几行就是 retn :


aa.png (33.04 KB, 下载次数: 0)
下载附件
2020-11-26 15:05 上传

这里就是消息处理了, 一直往上翻,就是所有消息(包括F10)的处理方法,这可是个大宝藏啊!
当你一直往上翻,凡是看到  Switch (cases 65..73) 之类的语句,就下个 F2 断点 ……
然后转到游戏窗口,按一下 F1,在这里断下:


aa.png (59.54 KB, 下载次数: 0)
下载附件
2020-11-26 15:17 上传

上面我们修改exe的资源,设定了 F1 是 1024,现在 [ebp+0x10] 果然出现了 0x400,就是 1024。
[color=]温馨提示:这里是整个《空当接龙》的核心,是个大宝藏,整个游戏所有快捷键(菜单项)都在这里实现,包括F2开局、F3选局 ……
[color=]你只要拦截这里,就能看到《空当接龙》所有核心过程。
【原版F10的秘密】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
现在用户按下了 F1,我们站在大宝藏里,要实现“无限撤销”。但是问题来了,具体怎么实现呢?
比如那些“回退数据”,到底是表示 梅花5 还是 黑桃A 呢?是第几行第几列呢?
说实在,这个过程太复杂了,我们自己是无法实现的,毕竟手上没有“源代码”。
但是我们这样想,“原版F10”不就是现成的功能吗?直接利用它不就行了吗?
好,刚才我们用小本本记下了  原版 VK_F10 的消息是  115 (十进制),也就是  0x73
现在把 [ebp+0x10] 的数值改成 0x73,骗过exe,让它以为是原版F10,继续执行,来到这里:


aa.png (41.19 KB, 下载次数: 0)
下载附件
2020-11-26 15:50 上传

你会惊奇地看到,这里就两句,一个 push,一个 call,然后就结束了,jmp 到 retn 那里去了。
这么神奇?现在跟进到 01003FCD 里面,然后就看到熟悉的画面:


120303pjeaonocndiimzec.png (20.47 KB, 下载次数: 0)
下载附件
2020-11-26 17:45 上传

上面说到,回退数据保存在 [0x1007984] 和 0x010079C0 ,而在整个“回退”过程中,就是用到这两个关键数据。
也就是说,我们 事先 在这两个地方 填写好回退数据,然后 一个 push 一个 call,就搞定了!
简直了!这年薪百万的程序员 也太滋味了吧?(黑人问号)
啥?回退数据从哪里来? ……前面不是说了嘛,百度网盘呀 ……(黑人晕菜)
现在就差这个 push [ebp+0x8] ,还是有必要搞清楚的,万一它包含着什么数据结构呢?
给 [ebp+0x8] 下一个“硬件读取”断点,结果发现,原来它就是游戏窗口的 Handle !
说白了,它就是一个整型数值,直接使用就行了。
如果非要追根究底(这种精神值得大力表扬),可以拦截  CreateWindowExW,返回 eax 就是窗口句柄。而且你会发现这个 Handle 保存在 [0x01008374],可以随时拿去用,比如修改窗口标题……


aa.png (37.95 KB, 下载次数: 0)
下载附件
2020-11-26 16:13 上传

【实现回退】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
回退的过程大致如下,用示意性的代码来表述如下:
[Asm] 纯文本查看 复制代码
-
01001E91  movzx eax,word ptr [ebp+0x10]
01001E95  add eax,-0x65                         ;  Switch (cases 65..73)
01001E98  cmp eax,0xE                           ;  拦截下句,处理扩展功能
01001E9B  ja                            ;  这里跳转到扩充代码
; 把回退数据拷贝到exe
mov esi, 我的自留地
mov edi, 0x010079C0
mov ecx,[0x1007984]
shl ecx,4
rep movSB
; 执行原版F10回退
push  [0x01008374]
call 01003FCD
; 继续回到原来的流程
jmp 010020F1
-
【败局处理】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
败局就是“死局”,没法再移动了,这时原版会弹出“你已经走投无路了”提示框。虽然你可以重新开始(同一局),但是你只能从头开始了。
但是,我们既然实现了“无限撤销”,就应该给用户“回退”的机会,这样才是“友好”的,才是人性化的。
所以,现在的处理方式是:
1、在失败提示出来之前,拦截下来
2、给一个提示,告诉用户可以回退,继续挑战
3、回到游戏,让用户继续玩
在文章《[原创]空当接龙逆向算法分析》中谈到“死局”的判定,容易找到相应的代码,进行修改即可。
这里不能放链接,大家网搜就行。
【细节处理】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
还有一些细节,在处理的时候需要注意,比如:
1、开局的时候,也就是新的一局开始时,应该把“自留地”初始化,删除“上一局”的回退数据
2、胜局的时候(通关之后),牌局会自动清除,这时用户通常都 不会 故意按下 F1/F10,但是万一按了,就可能出现不可预料的情况。作为严谨性,我们要进行处理,比如在回退之前判断一下牌局是否已经结束
3、回退之后,要清除“当前点击纸牌”。意思是,用户可能点了一下纸牌(第3列第5张),没有移走,就按下 F1,这样回退之后,(第3列第5张)纸牌还是“被点击”的(变成反色),这是不对的,所以要处理
类似这些细节都是实践中发现的,做事严谨认真的人,在生活和工作中都是受欢迎的。
【存档与读档】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
说实话,对于《空当接龙》这个游戏而言,我认为“存档”功能有点鸡肋,尤其是现在加入了无限撤销功能之后,“存档”的迫切性更加降低了。
不过,既然难得搞一次,就做得完善一些吧,而且“有”总比“没有”要好,可以应付不时之需。比如玩到一半,老妈喊“吃饭喽”,老婆喊“收快递”,也不怕了。
基于这种考虑,我决定不搞那种“N多存档”的功能了,就搞单一存档,保存当前正在进行的牌局,下次读取后继续玩。
【存档的数据】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
存档就是为了下次恢复盘面,要做到100%复原,而且我希望复员后,用户照样可以无限撤销,那就是“回退数据”都要保存。
为了方便用户,在菜单上加了“保存当前牌局(F6)”快捷键:


aa.png (28.29 KB, 下载次数: 0)
下载附件
2020-11-26 18:49 上传

经过仔细跟踪,以下数据都要保存:
[Delphi] 纯文本查看 复制代码
-
//当前牌局ID
  fs.Write(PChar($0100834C), 4);
  //剩余纸牌数量
  fs.Write(PChar($01007800), 4);
  //中转站
  fs.Write(PChar($01008330), 16);
  //终点站
  fs.Write(PChar($01008360), 16);
  //当前牌面52张
  fs.Write(PChar($01007500), 189*4);
//保存回退数据
//……
//……
//……
-
【读档恢复】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
读档过程基本等于存档过程,只不过“源地址”和“目的地”反过来了。
但是,读档要处理的细节很多,包括:
1、当前牌局怎么办?是否提醒用户结束当前正在玩的牌局?
2、如果是,那么还要做一遍清理工作
3、读档后,相当于开始新的一局,有很多地方需要初始化
对于这些繁琐的步骤,如果全部搞一遍,那就太不划算了。但是,这3步不就是玩家“选局(F3)”的过程吗?
所以,我决定把“读档”功能放进”选局(F3)”过程,正好原版exe规定不能输入0,必须 1 ~100万,那么就用0表示“读档”。
这样过程就理顺了,你点“选局(F3)”,首先问你是否放弃当前牌局,然后自动清理内存,再选局,恢复盘面,时间刚刚好,不迟又不早。
【运用DLL】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
存档和读档牵涉到文件操作,包括:创建、打开、写入、读取、关闭,这些过程有很多参数,用汇编来写的话非常吃力,而且容易出错,也不便于维护。
但是对于高级语言(VC、Delphi)来说,这都是小菜一碟。代码一打,参数自动蹦出来,还帮你“自动完成”。
因此《JF接龙》引入了一个辅助DLL,用来处理文件操作。
另外,既然有了DLL,还有更多的工作可以交给它,毕竟用高级语言可以实现更强大的功能。
其实DLL的编写并不难,用VC、Delphi、甚至VB都行(VB就不推荐了),对于exe这边(汇编语言),在初始化时调入DLL,取得功能指针,就是一个地址,以后 call 这个地址就行了。
在 freecell.exe 里就有现成的 LoadLibraryA 和 GetProcAddress,因为它本身要调用 cards.dll 。
搭建一个DLL并不需要多少时间,但是磨刀不误砍柴工,一旦搭建完成,你就拥有无穷的扩展空间。
比如,下面这个游戏,右上角显示敌人的数量。这些图片是原版游戏没有的,要自己加上去。
为了美观起见,图片具有半透明,让背景的游戏画面可以“透出来”。这就需要png图片。
但是你想想,用 汇编代码 怎么可能解码png图片?这不是天方夜谭吗?
但是,用高级语言编写DLL,这个优势就显示出来。解码png的lib都是现成的(VC、Delphi……),拿来用就是了。
另外,像图片、音乐这些资源,总不能全部塞进exe里面吧?而且还是破解方式。
但是你可以放进DLL里,或者通过DLL读取外部资源 ……
这就是所说的,一旦你引进了DLL,就打开了一片广阔无垠的天地。


2020-11-26_18-20-23.jpg (359.68 KB, 下载次数: 0)
下载附件
2020-11-26 18:33 上传

【游戏下载】
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
说了半天,差点忘了把游戏放上来。
下载后请看《使用说明》。
游戏目录中还包含 WinXP 原版《空当接龙》,以及帮助文件(*.chm),希望完整保留昔日的面貌。在可见的将来,WinXP只会在虚拟机中出现了。
感谢大家的支持,有空回复各位的询问。
[2021-01-02]更新说明
自从《JF接龙》发布后,有网友反映,在某些情况下《JF接龙》会出现“丢牌”现象。我们立即进行核查,但是由于每个人玩牌的习惯不同,即使是“同一牌局”,所经历的步骤(包括撤销)都不相同,所以我们一直无法“重现”网友反映的故障情况。因而,也就无法进行下一步“除错”工作。
虽然这样,我们相信网友反映的情况是真实的,所以一直没有放弃查找故障。
一次很偶然的机会,我们终于碰到了这种“丢牌”现象,这样就很快找到了原因,并修正了这个隐患。
请各位下载这个更新版本,感谢大家的支持!
【2021-01-02】《JF接龙》更新版:

[01-02-更新]JF_FreeCell.rar
(317.3 KB, 下载次数: 173)
2021-1-2 16:36 上传
点击文件名下载附件
下载积分: 吾爱币 -1 CB

备用下载:
https://wwi.lanzouw.com/i9L2Yjyfdpg
--------------------------------------------------------------------------------
《JF接龙》首发版(有bug):

JF_FreeCell.rar
(344.65 KB, 下载次数: 219)
2020-11-26 18:51 上传
点击文件名下载附件
下载积分: 吾爱币 -1 CB

备用下载:
https://www.lanzoui.com/iLLUAist13e

下载次数, 数据

jiangteddy   

大佬厉害,QQ斗地主能方便添加个悔牌功能么
水泥竹   

你们的电脑真好,还能安装XP。
k110130   

厉害了,感谢分享
liu5555   

楼主厉害。
2623666   

俺一直在玩呢  XP的空当接龙才是真经典 只能撤一次算是挑战了  win7那种无限撤回的没一点益智了
win95 98的关数又太少 所以还是xp好玩
shaokui123   

期待更新,学习高手经验
wscsj1128   

为了玩上空当接龙硬装上了虚拟机
aq24   

前排追更
学一下,完结了尝试一波(建议附上附件,方便学习)
此外推荐用md写,排版看起来很舒服
水泥竹   

请问大神,如何更换KDJL的底色呢?
您需要登录后才可以回帖 登录 | 立即注册

返回顶部