反编译 绿宝石改版双打bug——原理分析

起因:
本人对剧情无感,但热衷于刷双打对战塔。
三代只有绿宝石有双打对战塔,而且与以后世代区别比较大。
四代开始双打基本稳定,后世代只是在四代的基础上添砖加瓦。
网上各路改版将绿宝石对战系统升级,并添加后世代宝可梦,非常符合我的口味,遂开始尝试各种改版刷对战塔。
但似乎所有升级了对战系统的改版都是在DizzyEgg的原始版本上加工而成,所有的版本都有相同的bug会导致死机。

Bug展示:
青铜钟用Trick Room(戏法空间),非必要,只为比对方先出手,樱花儿使用Protect(守住)
1.PNG
青铜钟成功开出戏法空
3.PNG
青铜钟使用Explosion(大爆炸)
2.PNG
樱花儿被炸死,青铜钟也挂了,对面两个都在
3.PNG
对面使用Surf(冲浪), 死机,樱花儿的血条又被显示出来了
4.PNG

尝试了数个改版,都是类似问题,非常不爽。
由于懒得分析gba反汇编代码,遂弃坑gba很长一段时间。
近日,机缘巧合之下在饭堂搜到一高质量绿宝石c语言重写版:https://game.pokefans.cn/?p=824
源代码都有了,折腾吧。
按照INSTALL.md的步骤一步一步来,生成了pokeemerald.gba
一路打到对战塔,才发现居然有内置修改器:ROFLMAO:,早知道先分析代码了
赶紧尝试了一下双打,bug重现。
分析代码,问题出在NoTargetPresent()里: 捕获.PNG
字面意思,没有目标存在->有目标存在返回FALSE, 没有目标存在返回TRUE。
枚举变量MOVE_TARGET_FOES_AND_ALLY,字面意思,技能目标,敌人和盟友。
显然,冲浪会攻击到盟友,应该会跳入该代码段。
通过IsBattlerAlive()函数来判断目标,目标的盟友,和攻击者的盟友是否存活。
根据上面弄出bug的情况,这里应该返回FALSE(有目标存在),因为目标已死,目标盟友已死,攻击者的盟友存活。
乍看之下没啥问题,符合逻辑,接着找调用函数的地方:
捕获1.PNG
if条件下有个显眼的函数CancelMultiTurnMoves(), 字面意思,取消多重攻击技能。
这里盟友还活着,返回FALSE不取消是对的。
问题出在接下来的地方用到了gBattlerTarget,我方双方都挂了,显然用哪个都不合适。
再看看NoTargetPresent()里,一开始会用IsBattlerAlive()来判断gBattlerTarget是否存活,如果挂了的话会把gBattlerTarget设为盟友。
问题就出在当攻击方使用全体攻击技能,对方都挂了,只有盟友存活的时候gBattlerTarget是个无效值。
好了,问题已经找到,修复如下:
1667484634254.png
使用全体攻击时,当目标双方都挂了,将gBattlerTarget设置成为盟友。
编译后重试,确认bug被修复,可以愉快的刷对战塔了!

思考:
绿宝石原版会有类似的问题吗?
由于绿宝石原版双打对战机制和后世代不同,当一方有人挂了,而场下还有替补存活的时候会直接上场,所以不存在这个问题,这个bug是由升级对战系统导致的。

尝试:
之前很火的究极绿宝石好像死机问题比较少,是不是修复了这个bug?
试了一下,究绿很明显发现了这个问题,但并没有修复,而是尽量去绕过这个bug: 敌人的对战技能里没有攻击全体的技能(至少我没发现), 最新版甚至连学大爆炸的途径都没有。
既然没修复,有好奇心的可以尝试去复现一下这个bug:
留敌人双方一点血,保证能被我方多人攻击技能一下打死,
我方先手攻击者用多人攻击技能把对面两个都带走
我方后手攻击者用全体技能(地震,冲浪,放电)攻击
看看能不能重现这个bug:)
 
最后编辑:

norman

宝可梦训练家
成员
2022-11-01
3
6
195
bug找的很棒,但是似乎解决办法有点不够严谨?
NoTargetPresent函数最开始有考虑了目标阵亡的问题 在那个用到了GetMoveTarget函数来处理这种情况,不过最重要的是这个函数在最后有一句
*(gBattleStruct->moveTarget + gBattlerAttacker) = targetBattler;
而这个moveTarget的数组在整个代码里遍历一下会发现有些地方判断了这个参数和gBattlerTarget是否相等,比如战斗形态和自信过剩这种击倒类特性(战斗形态双打有bug不好生效,暂不讨论)
所以大胆猜测一下,双打bug处理了但自信过剩击倒了队友不会触发
BC9FHo8F6T.gif
(我这里为了测试方便把木守宫特性调成了自信过剩)
pokesinblack.png
个人觉得if里加一条gBattleStruct->moveTarget的赋值应该会更好?
另外egg的引擎也很老了整个一屎山,rh也不怎么查bug不像cfru特意做了个战斗塔来测试,能发现有这种bug说明楼主玩的很细致了🤧
 

QeaghX

一般路过の群众
元老
2022-08-04
25
22
968
四川成都
bug找的很棒,但是似乎解决办法有点不够严谨?
NoTargetPresent函数最开始有考虑了目标阵亡的问题 在那个用到了GetMoveTarget函数来处理这种情况,不过最重要的是这个函数在最后有一句
*(gBattleStruct->moveTarget + gBattlerAttacker) = targetBattler;
而这个moveTarget的数组在整个代码里遍历一下会发现有些地方判断了这个参数和gBattlerTarget是否相等,比如战斗形态和自信过剩这种击倒类特性(战斗形态双打有bug不好生效,暂不讨论)
所以大胆猜测一下,双打bug处理了但自信过剩击倒了队友不会触发
浏览附件397
(我这里为了测试方便把木守宫特性调成了自信过剩)
浏览附件398
个人觉得if里加一条gBattleStruct->moveTarget的赋值应该会更好?
另外egg的引擎也很老了整个一屎山,rh也不怎么查bug不像cfru特意做了个战斗塔来测试,能发现有这种bug说明楼主玩的很细致了🤧
大佬牛逼
 

幻魂小泡

宝可梦训练家
成员
2022-11-04
1
0
170
23
bug找的很棒,但是似乎解决办法有点不够严谨?
NoTargetPresent函数最开始有考虑了目标阵亡的问题 在那个用到了GetMoveTarget函数来处理这种情况,不过最重要的是这个函数在最后有一句
*(gBattleStruct->moveTarget + gBattlerAttacker) = targetBattler;
而这个moveTarget的数组在整个代码里遍历一下会发现有些地方判断了这个参数和gBattlerTarget是否相等,比如战斗形态和自信过剩这种击倒类特性(战斗形态双打有bug不好生效,暂不讨论)
所以大胆猜测一下,双打bug处理了但自信过剩击倒了队友不会触发
浏览附件397
(我这里为了测试方便把木守宫特性调成了自信过剩)
浏览附件398
个人觉得if里加一条gBattleStruct->moveTarget的赋值应该会更好?
另外egg的引擎也很老了整个一屎山,rh也不怎么查bug不像cfru特意做了个战斗塔来测试,能发现有这种bug说明楼主玩的很细致了🤧
大佬牛逼
 

Я929

宝可梦训练家
成员
2022-11-03
3
7
85
124
bug找的很棒,但是似乎解决办法有点不够严谨?
NoTargetPresent函数最开始有考虑了目标阵亡的问题 在那个用到了GetMoveTarget函数来处理这种情况,不过最重要的是这个函数在最后有一句
*(gBattleStruct->moveTarget + gBattlerAttacker) = targetBattler;
而这个moveTarget的数组在整个代码里遍历一下会发现有些地方判断了这个参数和gBattlerTarget是否相等,比如战斗形态和自信过剩这种击倒类特性(战斗形态双打有bug不好生效,暂不讨论)
所以大胆猜测一下,双打bug处理了但自信过剩击倒了队友不会触发
浏览附件397
(我这里为了测试方便把木守宫特性调成了自信过剩)
浏览附件398
个人觉得if里加一条gBattleStruct->moveTarget的赋值应该会更好?
另外egg的引擎也很老了整个一屎山,rh也不怎么查bug不像cfru特意做了个战斗塔来测试,能发现有这种bug说明楼主玩的很细致了🤧
感谢指正,确实需要gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget
双打其实bug不少,死机的bug除了这个又发现2个:
1. 吸取种子攻击已阵亡对象->没有判断gBattlerTarget是否存活
2. 大晴天下樱花儿死亡->变身代码少写个else
不死机的bug:
mega图标显示错乱的问题->CreateBattlerHealthboxSprites()里调用CreateMegaIndicatorSprite(),CreateMegaIndicatorSprite()里用到的 gHealthboxSpriteIds又是在CreateBattlerHealthboxSprites()之后被赋值的,导致如果gHealthboxSpriteIds里的id被反复分配,老的SpriteId已无效,但新的SpriteId和老的重复且不在一个位置上就会出问题。
总结下来就是rh的人根本不玩双打,单打剧情加通关的质量其实还说的过去。
 
最后编辑:

norman

宝可梦训练家
成员
2022-11-01
3
6
195
感谢指正,确实需要gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget
双打其实bug不少,死机的bug除了这个又发现2个:
1. 吸取种子攻击已阵亡对象->没有判断gBattlerTarget是否存活
2. 大晴天下樱花儿死亡->变身代码少写个else
不死机的bug:
mega图标显示错乱的问题->CreateBattlerHealthboxSprites()里调用CreateMegaIndicatorSprite(),CreateMegaIndicatorSprite()里用到的 gHealthboxSpriteIds又是在CreateBattlerHealthboxSprites()之后被赋值的,导致如果gHealthboxSpriteIds里的id被反复分配,老的SpriteId已无效,但新的SpriteId和老的重复且不在一个位置上就会出问题。
总结下来就是rh的人根本不玩双打,单打剧情加通关的质量其实还说的过去。
方便细说一下这两个死机bug的触发过程吗 感觉应该已经修复过了
另外查了一下老外的聊天记录,他们似乎早就知道这个target的问题了,但不知道为啥迟迟不修复 新版本bug依旧存在但是不至于死机 只是会诈尸一下- -
 
最后编辑:

Я929

宝可梦训练家
成员
2022-11-03
3
7
85
124
方便细说一下这两个死机bug的触发过程吗 感觉应该已经修复过了
刚才确认了一下,吸取种子并没有问题,真正引发问题的是花之礼。
不是100%死机,试了好久没重现,步骤如下:
开启大晴天,樱花儿变身,在晴天消失之前被打死,被打死后仍有二次变身信息。
可以看到明显的逻辑错误:
4092 if (gBattleMons[battler].hp == 0)
4093 ret = 0;
4094#if B_WEATHER_FORMS >= GEN_5
4095 else if (GetBattlerAbility(battler) != ABILITY_FLOWER_GIF)
这里少了个else,导致hp等于0时返回值不一定是0。

1667572264734.png
 

在线成员

现在没有会员在线。

论坛统计

主题
338
消息
1,551
会员
2,308
最新会员
shix1937

关于我们

  • 宝可梦以及其他相关名称是任天堂的商标,版权归宝可梦公司所有。宝可饭堂是一个同人游戏中文社区,不隶属于宝可梦公司。在宝可饭堂上的粉丝游戏亦未获得其授权,请支持正版游戏。
© 2022- pokefans.cn
点此延长宝可饭堂生命