玩了东方夜雀食堂一段时间后,我实在是受不了了。
虽然这游戏给了一个关闭符卡动画的选项,但是符卡附带的技能特效还是会出现。
这其实也没啥。问题是有些稀客的符卡特效做的实在是一言难尽。乱七八糟、刺眼夺目、大红大紫、土味十足、俗不可耐,关键是还TM强迫观看。就比如黑谷山女的特效:

一晚上放个五次她的符卡,我觉得我的眼睛都要被闪瞎了,制作组不会觉得这种特效很cool吧?
半年前我就跟制作组反映过这个问题,但是直到现在我也没找到关闭特效的选项。
那只能自己动手,丰衣足食了。
本文的所有地址基于steam 2.4.1d版本
作者已经提前导出了Assembly-CSharp.dll。方法与大侠立志传安装mod的方法一模一样,只是安装时有可能需要梯子来下载必要文件。
使用的CE版本为7.5,且确保了Activate mono features被激活
首先,需要选择一个切入点。
既然是想要修改符卡特效,必定要先找到这个符卡。
通常来说,符卡释放后会带来一系列的改变。通过这些变动下断,然后反追上去,一般来说就能找到想要的信息。
那么符卡释放后,会有什么变化呢?
比较典型的,一个是符卡的buff时间会增加,另一个是场面上总buff的个数会增加。
这里我选择的切入点是buff总个数,因为符卡的释放有可能会造成储存buff时间的地址变动,所以不太好追。而buff个数是固定的地址,所以更加容易上手一些。
初始数量搜0-10,然后每增加1buff,搜+1,每减少搜-1,如此反复,得到储存buff数量的地址:15516C346E4
查找什么改写了该地址,然后用黑谷释放一次符卡,得到下图:

追过去,下断,然后重新用黑谷释放符卡:

同时我们也得到了return address:

那么接下来就是定位每一个上层函数,查看函数名以及附近调用的函数。
GameAssembly.dll+12C46AF:

看不出来啥,继续往上追。
GameAssembly.dll+A11F2B:

函数头同样看不出来东西,继续往上追。
GameAssembly.dll+669B49:

这里开始有点意思了,继续往上,看看上层函数叫啥。
GameAssembly.dll+66E09E:

这个从函数名来看,很明显就是黑谷的符卡效果,即:关店时客人不会离开。那么必定是先触发符卡,然后调用此函数。也就是说想要得到符卡信息还得往上走。
GameAssembly.dll+4CAF0D:

看这个函数名,有点像委托。
那么就去Assembly-CSharp.dll里搜一下"c__DisplayClass12_0 OnPositiveBuffExecute b__0",得到下图:

如果我们把游戏切换成英文版,就会发现黑谷山女的英文名就叫Yamame。
换言之,这个Spell_Yamame,其实就是黑谷的符卡信息。
那么我们看一下Spell_Yamame究竟是个啥,以及包含了哪些属性:

首先吧,既然是特效,那么它的类型就不可能是int,float,AudioClip。
排除这三个类型,那么只剩下GameObject类型的lightVFX,webVFX1,webVFX2。
再加上我想去除的特效是闪光棒,不是蜘蛛网。那基本上可以确定了,Spell_Yamame.lightVFX就是我想要去除的特效。
从Spell_Yamame的信息图里,我们可以看到,Spell_Yamame继承自SpellBase。
于是我在dnSpy里翻了N久的关键词搜索,终于给我找到了个static类型的函数:


可以看到,GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell 这个函数,只需要一个npc的id,然后就会返回该npc的SpellBase,把它强转一下,不就是Spell_Yamame了么?
int ID = 2000;//黑谷山女的ID
Spell_Yamame spell_Yamame = (Spell_Yamame)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(ID); 首先,我尝试的是直接去除特效,即:
int ID = 2000;//黑谷山女的ID
Spell_Yamame spell_Yamame = (Spell_Yamame)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(ID);
spell_Yamame.lightVFX = null; 但是很遗憾,一旦将特效设置为null,游戏虽然不会崩溃,但是黑谷会直接卡住不动,不吃饭也不点餐。
由于个人水平有限,我暂时没追到为啥VFX为null时人物会直接卡住。但是我转念一想,既然去除做不到,那就替换呗。
即:
int ID = 2000;//黑谷山女的ID
Spell_Yamame spell_Yamame = (Spell_Yamame)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(ID);
spell_Yamame.lightVFX = spell_Yamame.webVFX1; 于是那讨人厌、闪瞎眼的闪光棒特效就不见了,取而代之的是相对不起眼的蜘蛛网特效。
我觉得自己实在是太聪明了!!!
接下来无非是去游戏里找个非常不起眼,几乎等同于无的特效。最终我选择的是阿求的特效。
以下是我用来替换一些我不想看到的特效的代码:
public static void ReplaceVFX()
{
Spell_Akyuu spell_Akyuu = (Spell_Akyuu)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(3);
Unity.GameObject replacingEffect;
Unity.GameObject replacingEffect2;
if(spell_Akyuu != null)
{
replacingEffect = spell_Akyuu.rewardPic;
replacingEffect2 = spell_Akyuu.giveItem;
if(replacingEffect != null && replacingEffect2 != null)
{
Spell_Yamame spell_Yamame = (Spell_Yamame)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(2000);
if(spell_Yamame != null)
{
spell_Yamame.lightVFX = replacingEffect;//黑谷山女
}
Spell_Parsee spell_Parsee = (Spell_Parsee)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(2001);
if(spell_Parsee != null)
{
spell_Parsee.rewardObject = replacingEffect;//水桥帕露西
}
Spell_Marisa spell_Marisa = (Spell_Marisa)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(10);
if(spell_Marisa != null)
{
spell_Marisa.magicArrayEffect = replacingEffect;//雾雨魔理沙
spell_Marisa.magicArrayExplosion = replacingEffect;
spell_Marisa.mushroomMilkyWay = replacingEffect2;
}
Spell_Utsuho spell_Utsuho = (Spell_Utsuho)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(2005);
if(spell_Utsuho != null)
{
spell_Utsuho.tokamakEffect = replacingEffect;//灵乌路空
}
Spell_Patchouli spell_Patchouli = (Spell_Patchouli)GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell(27);
if(spell_Patchouli != null)
{
spell_Patchouli.philosopherStoneEffect = replacingEffect;//帕秋莉
}
}
else
{
return;
}
}
else
{
return;
}
} 但是!
这游戏好像不支持mod。
每次我使用BepInEx加载游戏到一半时,直接报错"Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object."
如果有大佬可以指点下这个问题,那真的感激不尽。
那么回归正题,既然mod patch不了,那我就直接把C#转成汇编,然后用线程调用汇编写成的函数。毕竟这个函数本质上来说很简单,唯一麻烦点的就是查下偏移。
以下是查找Spell_Yamame.lightVFX的例子。

打开CE里的Net Info,Image选择Assembly-CSharp.dll,class里搜索"Spell_Yamame",最后得出下图:

由图可知,lightVFX的偏移是0x28。
同理可以得知其他特效的偏移,最终通过CE,创建线程来执行我们的替换函数:
[ENABLE]
alloc(newmem,512)
createthread(newmem)
label(exit)
label(Yamame)
label(Parsee)
label(Marisa)
label(Utsuho)
label(Patchouli)
newmem:
sub rsp,28
mov rcx,(int)3//稗田阿求
call GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell
test rax,rax
je exit
mov rsi,[rax+48]//Unity.GameObject replacingEffect = Spell_Akyuu.rewardPic
test rsi,rsi
je exit
mov rdi,[rax+28]//Unity.GameObject replacingEffect2 = Spell_Akyuu.giveItem
test rdi,rdi
je exit
Yamame:
mov rcx,(int)2000//黑谷山女
call GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell
test rax,rax
je Parsee
mov [rax+28],rsi//Spell_Yamame.lightVFX = replacingEffect
Parsee:
mov rcx,(int)2001//水桥帕露西
call GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell
test rax,rax
je Marisa
mov [rax+20],rsi//Spell_Parsee.rewardObject = replacingEffect
Marisa:
mov rcx,(int)10//雾雨魔理沙
call GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell
test rax,rax
je Utsuho
mov [rax+60],rsi//Spell_Marisa.magicArrayEffect = replacingEffect
mov [rax+68],rsi//Spell_Marisa.magicArrayExplosion = replacingEffect
mov [rax+70],rdi//Spell_Marisa.mushroomMilkyWay = replacingEffect2
Utsuho:
mov rcx,(int)2005//灵乌路空
call GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell
test rax,rax
je Patchouli
mov [rax+38],rsi//Spell_Utsuho.tokamakEffect = replacingEffect
Patchouli:
mov rcx,(int)27//帕秋莉
call GameData.Core.Collections.NightSceneUtility.DataBaseNight.RefSpecialGuestSpell
test rax,rax
je exit
mov [rax+30],rsi//Spell_Patchouli.philosopherStoneEffect = replacingEffect
exit:
add rsp,28
ret
[DISABLE]
dealloc(newmem,512)