用汇编修改dos版三国英杰传支持键盘操作__附带制作过程

waklin 2008-05-27 09:38:48
加精
下载地址
http://pickup.mofile.com/1817884968502331
将附件解压后替换到游戏目录下,按esc进入游戏,即可用键盘的空格键代替鼠标左键,ESC代替鼠标右键 (没有再db4的环境下测试,理论上没有问题)。

首先感谢论坛里的 DelphiGuy 给予技术上的帮助。


这个游戏相信很多朋友都玩过吧,这是我接触pc后第一个真正意义上的游戏,99年那时我大一,还不知道编程是什么的时候,便偷偷的用软盘将游戏和存档copy到机房的机器上,从机房开门一直玩到管机房的老师下班,当时真的是废寝忘食,比后来在网吧通宵玩传奇是有过之而无不及,但是奇怪我这么努力却一直没有将这个游戏打通关,可能是存档丢了无数次,然后无数次的重来耽误太多宝贵时间的缘故,游戏记录也一直停留在博望坡一役。
记得当时好像有两个游戏供选择,一个是三国志V和三国英杰传,由于前者需要两张软盘才装的下,想当时一张软盘要5元,1M折合RMB要3.47元,再看看现在的存储设备真是便宜,真的感觉自己是生在"旧社会"。所以选择了后者,但是从此也就和这个游戏结下了不解之缘。


想法初现:
人总有些怀旧的情怀,工作了以后也试图几次自我突破要将这个游戏通关以弥补当年的遗憾,但是均已失败告终。经过分析有三大原因导致:
1.游戏太难了,不适合我?
2.游戏不能随时存档,有时一场战役输了,需要从前面很远的地方重新来过,无疑是对人毅力极大的考验。
3.游戏全程只能用鼠标操作,要知道一直点鼠标左键是件很痛苦的事情,连游戏过程中的过场对白都无法用键盘跳过,需要用鼠标单击,无疑是对食指极大的考验 可能鼠标点到博望坡已经是我食指的极限了。
我也针对每个原因寻找对策
1.网络发达了,可以到网络上找攻略,到论坛看看高手的心得,哇,原来高手这么多,将所有武将练到99级的大侠比比皆是,论智慧,论武功我都都不比他们差o(∩_∩)o...,我怎么就不行呢?看来这个不是原因。
2.后来听说gba上的版本比较简单,而且可以随时存档,这下爽啦,于是在psp上装上gba模拟器,但是进入游戏发现和pc版本还是有一定区别,我还是对pc版的画面情有独钟,玩了一阵就没有继续下去了。
3.自己动手,丰衣足食,让游戏支持键盘操作。

开始动手:

首次碰壁:
1.其实早在多年前就有了这个想法,刚开始有这个计划时以为很简单。无非就是模拟鼠标操作:安装全局键盘钩子,当拦截到空格键按下便向游戏窗口发送鼠标左键单击的消息。早在玩diablo(暗黑破坏神),就因为alt键需要一直按下才能显示地上掉落的物品写过一个辅助的程序,按一下alt键显示掉落物品,再按一下alt键不显示。这个应当跟那个也差不多。
但是实际操作中才发现挑战才刚刚开始,由于dos的游戏在windows下运行其实相当于windows中虚拟了一个dos环境,可以认为是个虚拟机,然后在这个虚拟dos中运行dos程序,就象我们编程中编写的控制台程序(console application),是没有消息循环的。所以向它发送消息等于对牛弹琴。
这下麻烦了,就象我只会开汽车,现在要我去开手扶拖拉机,虽然都是开车,但我发现原来车没有方向盘我是不会的,只好作罢。用外挂的方式是搞不定了,只能修改游戏本身了,但是受技术所限,感觉就向拿个苍蝇拍打苍蝇,突然要去对付一只大象,根本不知道如何下手,只好悻悻的作罢。但是心中一直有一个疙瘩在。


卷土重来:
(注意:此节部分文字有几分演绎的成分,如果你有头晕呕吐的现象请直接跳到下一节,本人不对上述现象产生的后果承担任何责任)
就这样经过了很多年,忽然有一天因为对破解感兴趣重新拾起汇编开始学习的时候,当看到"中断"那一章的时候,突然灵光一现,那感觉就想黑暗夜空中划过的一道闪电,对,就是"键盘中断",经历过dos时代编程的人都应该对中断了如指掌,那感情就象我们现在和windows的Api函数一样,虽然你可能感受不到,但它却无处不在。但是象我这个时代接触编程的时候就已经是win98了。 那时dos基本上已经扔进垃圾箱,但是掌握基本的dos命令在当时那还是相当有面子的事情,在给女同学重装系统或格式化、分区的时候她们对你那种钦佩的眼神绝对会让你虚荣心得到极大满足^_^。
但是一切就象王朝更替一样,在掌握了dos时代编程的人当然会反对windows的到来,因为他们的地位受到了动摇,本来是领先的,现在却要站在同一起跑线上。一个新的时代就要到来,也终将会到来。这让我想起了<兵临城下>电影里瓦西里被问到:"战争结束后你会去干什么?"的感觉一样,集所有荣誉为一身的民族英雄又要和平凡人一样为了生活奔波,那种落差……

有点跑题了,其实刚才就像"中断",我突然想去抒发一下感想,抒发完感想后继续回到我的主题。在搞清楚了中断是怎么回事后,仿佛看到了通往成功的道路已经出现一点光亮了,但是也仅仅是一点点。原因如下
1.要想实现目的必须要修改游戏文件,在游戏文件中改变其"键盘中断向量"为我自己的"键盘中断处理程序"入口,在我自己的"键盘中断处理程序"中拦截按键的扫描码,然后触发鼠标按下的操作。而这一切都要在游戏运行中完成(游戏运行时我才能知道"键盘中断向量"保存的原始"键盘中断处理程序"地址,因为在我自己的"键盘中断处理程序"中还要调用原始的"键盘中断处理程序",听起来有点绕,但是的确如此,找不到更易理解的描述,用windows角度看就向我们安装的钩子要callnexthook一样),但是由于游戏是dos格式,而我对dos环境的文件格式一无所知。希望比windows的pe格式简单。
2.如何触发鼠标按下的操作,本来以为这一步很简单,事实证明这才是整个程序的重中之重,难中之难(对我而言) ,因为涉及到鼠标中断及鼠标事件处理程序的处理。
3.需要用dos时代的16位汇编进行上述代码的编写,想到要处理段寄存器,偏移地址就头疼。

当时的情况让我想起一句话,有困难要上,没有困难创造困难也要上,还是一步一步来吧。

大功告成:
思路已经有了,但是如何实现呢?首先从dos文件格式入手,很庆幸,这个游戏的入口文件是个.COM格式的文件(REKO3IBM.COM),COM文件是一种单段执行结构,起源于CPM-86操作系统,其执行文件代码和执行时内存影象完全相同,其始执行偏移地址为100H,对应于文件的偏移0。这下好办了

用汇编写一段中间代码,代码实现的主要功能如下:
1.我的"键盘中断处理程序"的代码
拦截键盘的扫描码,如果是空格键那么修改我维护的鼠标按键状态
调用原始的"键盘中断处理程序"

2.我的"鼠标中断处理程序"的代码
拦截游戏调用3号功能获取鼠标信息,将自己维护的鼠标状态返回
拦截游戏中调用12号功能替换“鼠标事件处理程序”,将“鼠标事件处理程序”换成自己的,同时保存游戏的“鼠标事件处理程序”入口地址

3.我的"鼠标事件处理程序"的代码
将鼠标状态(纵、横坐标,按键状态,偏移量)记录到我维护的内存单元中
调用游戏的“鼠标事件处理程序”

4.安装以上三段代码到指定的的内存中(我用的是0:200h处)

5.将键盘中断、鼠标中断的入口改为我的,保存原有的入口地址。

5.恢复游戏的开头几个字节并跳转到游戏开头运行。

将中间代码编译成exe文件(mid.exe)后, 用vc写一个文件读写的工具,将mid.exe的内容(实际上是一堆二进制的指令)写入到REKO3IBM.COM的末尾,下面要做的工作就是让游戏运行时首先跳到我附加的代码处开始执行,只要修改REKO3IBM.COM的开头5个字节,跳转到我的代码执行完毕后又将控制权交还给游戏。这样就完成了偷天换日,实际上很多木马程序就是这么干的。

终于在一个周末的下午将上述功能全部搞定,本来打算主要写一写技术细节方面的东西,谁知道洋洋洒洒写了这么多无关紧要的东西,很多实现过程的中很多细节都没有描述到,等有时间再整理整理,重点把实现过程写出来。
...全文
4198 49 打赏 收藏 转发到动态 举报
写回复
用AI写文章
49 条回复
切换为时间正序
请发表友善的回复…
发表回复
DrsExplorer 2011-08-14
  • 打赏
  • 举报
回复
厉害!!!!
cnzuo 2011-08-03
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 sxcong 的回复:]
这个游戏其实不难打。我第一次打碰到吕布抢徐州就过不去了。
后来有了耐心,每关都拖到30合,每人升级都很快,并且注意培养好供给部队或军乐队。里面所有的战役全打了一遍,包括伐东吴,全部打通,百战百胜。
游戏倒是不难,就是为了长经验值,明明一刀就灭掉的, 还非要用混乱假情报挑逗他,或者逼着他砍你一刀,然后你再用恢复计来恢复兵力。还要小心不能让他砍着我方的山贼什么的 ,防止反击一下就挂了。
这么点的……
[/Quote]
主要原因是那阵游戏太少了,现在的游戏太多了。
ddj008 2011-07-02
  • 打赏
  • 举报
回复
真厉害!!!高手啊
swordsman 2011-07-01
  • 打赏
  • 举报
回复
我只是想得到分。
jiyanchang 2011-05-30
  • 打赏
  • 举报
回复
膜拜楼主
dbWindy 2011-01-02
  • 打赏
  • 举报
回复
abgood 2010-12-10
  • 打赏
  • 举报
回复
楼主真乃一神人,玩游戏玩到这个地步,佩服!!!
bobo98405208 2010-12-01
  • 打赏
  • 举报
回复
冒泡,收下这帖子
lumeng394356779 2009-09-24
  • 打赏
  • 举报
回复
菜鸟,在学习中

建立一个群,欢迎高手、菜鸟,一起来学习,一起讨论,一起进步

群号:85864592

新群,人员还不多
dyf0130 2009-07-22
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 aaaaaaa98 的回复:]
想不到有这么多同道,这游戏挺有意思,感觉最难是长板坡,但是只要每一关尽量升级,没有多大问题,包括关羽逃命
[/Quote]
最难是长板坡,传说中有人用兵堵住民众,还一点点的全歼敌人
俺长板坡上,只能防御,
长板坡下有1次也全歼敌人,好像只1次,可能也是最后1次
比较喜欢张了和许出,见他们1次,赵云关羽就升级1次

但是最后也就孙前(先是运粮车变成鼓号队),赵云,刘备达到了99级
ciahi 2008-06-05
  • 打赏
  • 举报
回复
现在对DOS没什么兴趣了~~~
用户 昵称 2008-06-04
  • 打赏
  • 举报
回复
俺更想听楼主讲故事。
waklin 2008-06-04
  • 打赏
  • 举报
回复
; 实现方法就是将下面的汇编代码编译成exe后,将exe中的信息copy到游戏文件的末尾,同时改变游戏文件开头的字节,让游戏一运行就跳转到末尾我们新增加的部分执行。
; 游戏运行时我管理的部分内存状态可参见http://blog.csdn.net/waklin/archive/2008/06/02/2505012.aspx

;=================================================
; 三国英杰传游戏修改(游戏中支持键盘)中间汇编代码
; 作者: waklin
; Email: xyc_cat@163.com
; QQ: 251710001
; 原理如下
; 1、游戏运行时改变键盘中断向量(int9)为自定义的键盘中断处理程序。
; 2、自定义的键盘处理程序中拦截SPACE、ESC按键的状态,分别用来模拟鼠标左键和鼠标右键按下/释放的操作。
; 3、游戏运行时改变鼠标中断向量(int33h)为自定义的鼠标中断处理程序。
; 4、自定义的鼠标中断处理中拦截3、12号功能:
; *** 核心部分 ***
; 12号功能为"为鼠标事件设置处理程序",将"鼠标事件处理程序"指向自定义的处理程序。
; 在自定义的"鼠标事件处理程序"中捕获所有mouse事件,保存mouse状态。
; 3号功能为"读取鼠标位置及其按钮状态",游戏中就是通过这个功能号来获取鼠标按键状态,判断用户是否按下鼠标操作。
; 用户程序读mouse状态的时候就把自己维护的状态返回给游戏。
; *** ******* ****
; 5、在自定义的键盘中断中触发SPACE、ESC键时,改变mouse状态。
; 版权所有:
; 日期:2008-05-24
;=================================================

assume cs:codesg

codesg segment

start: nop
jmp begin
strOutput1 db 'Modify By Waklin; Email: xyc_cat@163.com; QQ: 251710001',0
strOutput2 db 'Please Press KEY<ESC> Enter Game!',0

;=================================================
; 新的键盘中断处理程序
;=================================================
int9: push ax
push dx
push cx
push ds

mov ax,0
mov ds,ax

in al,60h ; 读取60h端口的键盘扫描码
pushf
call dword ptr ds:[200h] ; 调用旧的int9中断例程(旧的键盘中断处理程序的入口地址放在0:200h处)

spacepress:
cmp al,39h ; 按下SPACE键 (模拟鼠标左键按下)
jne spacerelease
mov byte ptr ds:[218h],1 ; 0:218h处保存按键状态位
jmp int9ret

spacerelease:
cmp al,0b9h ; 松开SPACE键 (模拟鼠标左键松开)
jne escpress
mov byte ptr ds:[218h],0
jmp int9ret

escpress: ; 按下ESC键 (模拟鼠标右键按下)
cmp al,01h
jne escrelease
mov byte ptr ds:[218h],10b
jmp int9ret

escrelease: ; 松开ESC键盘 (模拟鼠标右键松开)
cmp al,81h
jne int9ret
mov byte ptr ds:[218h],0

int9ret:pop ds
pop cx
pop dx
pop ax
iret
int9end:nop

;=================================================
; 新的鼠标中断处理程序
;=================================================
int33: push ds

push ax
mov ax,0
mov ds,ax
pop ax

; 拦截 为鼠标事件设置处理程序
cmp ax,0ch
jne three

; 记录原始鼠标事件处理过程地址到0:212h处
push dx
pop ds:[212h]
push es
pop ds:[214h]
push cx
pop ds:[216h]

; 改变dx、es的值
mov dx,2e0h

push ax
mov ax,0
mov es,ax
pop ax

mov cx,11111b ; 此处设置鼠标事件中断掩码(处理所有鼠标事件 bit5,bit6是鼠标中键不予处理)

; 拦截 读取鼠标位置及其按钮状态(游戏中在等待用户按下鼠标其实是在一个循环中调用鼠标中断03号功能获取鼠标信息)
three:
cmp ax,03h
jne old33

; 返回自己管理的鼠标状态
mov bl,ds:[218h] ; 按键状态
mov cx,ds:[20ah] ; 指针水平位置
mov dx,ds:[20ch] ; 指针垂直位置

jmp int33ret

old33: pushf
call dword ptr ds:[204h] ; 调用旧的int33h中断例程(旧的鼠标中断处理程序的入口地址放在0:204h处)

int33ret:pop ds
iret

int33end:
nop

;=================================================
; 自定义鼠标事件处理过程
;=================================================
mouseproc:push ax
push es

push ax ; 将中断向量压入栈

mov ax,0
mov es,ax

; 鼠标按钮状态
mov word ptr es:[208h],0

; 鼠标水平位置
mov word ptr es:[20ah],cx
; 鼠标垂直位置
mov word ptr es:[20ch],dx
; 水平位置变化量
mov word ptr es:[20eh],si
; 垂直位置变化量
mov word ptr es:[210h],di

pop ax

; 移动鼠标
move:
cmp ax,1
jne lpress
call dword ptr es:[212h] ; 调用游戏本身的鼠标事件处理程序
jmp procret

; 按下左按钮
lpress:
cmp ax,10b
jne lrelease
mov byte ptr es:[218h],1
jmp procret

; 释放左按钮
lrelease:
cmp ax,100b
jne rpress
mov byte ptr es:[218h],0
jmp procret

; 按下右按钮
rpress:
cmp ax,1000b
jne rrelease
mov byte ptr es:[218h],10b
jmp procret

; 释放右按钮
rrelease:
cmp ax,10000b
jne procret
mov byte ptr es:[218h],0

procret:pop es
pop ax
retf ; 由于CS发生了改变,此处一定要用reft(在这个地方吃了大亏,调试了一整个晚上)

procend:nop

;=================================================
; 安装新的键盘中断处理程序至0:230h处 (游戏运行时首先跳转到此处执行)
;=================================================
begin: mov ax,cs
mov ds,ax
mov si,offset int9
add si,981h + 100h ; 游戏代码长度 + 偏移量100h(*.com格式特性)

mov ax,0
mov es,ax
mov di,230h

mov cx,int9end - int9
cld
rep movsb

;=================================================
; 安装新的鼠标中断处理程序至0:280h处
;=================================================
mov ax,cs
mov ds,ax
mov si,offset int33
add si,981h + 100h

mov ax,0
mov es,ax
mov di,280h

mov cx,int33end - int33
cld
rep movsb

;=================================================
; 安装自定义鼠标事件处理过程至0:2e0h处
;=================================================
mov ax,cs
mov ds,ax
mov si,offset mouseproc
add si,981h + 100h

mov ax,0
mov es,ax
mov di,2e0h

mov cx,procend - mouseproc
cld
rep movsb

;=================================================
; 显示字符串
;=================================================
mov ax,0b800h
mov es,ax

; 显示字符串made by waklin!
mov si,0
mov di,0
mov cx,0
read1: mov cl, cs:strOutput1[981h + 100h + si] ; 读取字符串
jcxz dis2
mov es:[di],cl
inc si
add di,2
jmp read1

; 显示字符串please press esc enter game!
dis2: mov si,0
mov di,320
mov cx,0
read2: mov cl, cs:strOutput2[981h + 100h + si];
jcxz waitkey
mov es:[di],cl
inc si
add di,2
jmp read2

;=================================================
; 按ESC进入游戏
;=================================================
waitkey:mov ah,0
int 16h ; 读取按键
cmp al,1Bh
jnz waitkey

;=================================================
; 记录原始的键盘中断向量到0:200h处
;=================================================
mov ax,0
mov es,ax

push es:[9*4]
pop es:[200h]
push es:[9*4 + 2]
pop es:[202h]

;=================================================
; 设置新的键盘中断向量为0:230h
;=================================================
mov word ptr es:[9*4],230h
mov word ptr es:[9*4 + 2],0

;=================================================
; 记录原始的鼠标中断向量到0:204h处
;=================================================
push es:[33h*4]
pop es:[204h]
push es:[33h*4 + 2]
pop es:[206h]

;=================================================
; 设置新的鼠标中断向量为0:280h
;=================================================
mov word ptr es:[33h*4],280h
mov word ptr es:[33h*4 + 2],0

;=================================================
; 恢复程序开始的五个字节
;=================================================
mov word ptr cs:[100h],068Eh
mov word ptr cs:[102h],002Ch
mov byte ptr cs:[104h],0B4h

;=================================================
; 跳转到程序的开头 (完成所有替换工作,交还给游戏)
;=================================================
push cs
mov si,100h
push si
retf

codesg ends

end start
aoyihuashao 2008-06-03
  • 打赏
  • 举报
回复
很容易的,
我好像很快就通关了。

每回合每个武将尽量有动作,攻击或技能,这样升级很快。

记得文官有个技能好像叫献祭,是给对方加蓝的。

我三个文官对放这个技能。。。
LutzMark 2008-06-03
  • 打赏
  • 举报
回复
英杰传的贴必须顶。96到98年我玩了得有10遍,游戏性、音乐、画面在当时都是一流!
从开始的最后一关狂用炸弹勉强通关,到后来各种BT方法蹭经验每仗都打到最后一回合,连张飞都被我转职成军乐队,长板坡杀光曹军,主力全部99级...
还有炎龙骑士团也是经典
用户 昵称 2008-06-03
  • 打赏
  • 举报
回复
和众位一样的是,俺也非常喜欢这个游戏,不同的是,如果一个游戏不能改,俺一般不再玩,网络时代俺迅速的堕落就是一个证明。
dreamice01 2008-06-03
  • 打赏
  • 举报
回复
楼主水平高,小菜鸟佩服
dsdpz 2008-06-03
  • 打赏
  • 举报
回复
当时为了玩一个老游戏,叫啥 “巫师”的,DOS下的,比较早的FPS,画面没有一个十字叉,就用C写了一个TRS的驻留程序,直接写显存画了一个十字叉在画面上用来瞄准,哈哈,一样转得头昏。
dsdpz 2008-06-03
  • 打赏
  • 举报
回复
三国英杰传确实挺有意思,不过本人玩这个游戏比较晚,到2000年才第一次接触这个游戏,但是直到2003年才算完整的通关,哈哈,一个人闷在乡下,那时又没有上网,只好玩单机。美好的回忆!!!
臭亲亲 2008-06-03
  • 打赏
  • 举报
回复
佩服 LZ
加载更多回复(29)

21,458

社区成员

发帖
与我相关
我的任务
社区描述
汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。
社区管理员
  • 汇编语言
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧