XPE 詳細(xì)制作過程揭秘
今天來給大家揭秘一下XPE的完整修改流程。
從我怎么發(fā)現(xiàn)的這個問題,到我是如何分析這個問題的,最后再到我怎么解決這個問題。
接下來我將會詳細(xì)給大家介紹Ex:Phiedit是如何一步一步變成它現(xiàn)在的樣子的:
夢開始的地方:


經(jīng)過漫長的等待,RPE的本體終于出現(xiàn)在了我們面前:

先把官方的1.3HotFix補丁打上,1.3的Bug就已經(jīng)少了一半了。
之后我們進(jìn)入Resources\Textures。
不難發(fā)現(xiàn),這個文件夾中存放有大量未使用的按鈕貼圖,例如Add.png:

這個我不知道還有沒有哪個骨灰級玩家認(rèn)得它。
這個是遠(yuǎn)古時期PhiEditer的選歌界面中右上角那個添加歌曲的按鈕!
Re:Phiedit中根本就沒用過這張?zhí)麍D!
最離譜的是,這張?zhí)麍D居然一直跟著我們從1.0到1.1再到1.2再到現(xiàn)在的1.3從未被清理?。。?/p>
類似的還有delete1.png,icon\save.png,BlueBack.png等等。
cmdysj從來就沒清理過一次!
在Phigros自制譜的領(lǐng)域稱霸天下了就開始放水。
那就讓我來幫你吧。
只要刪完沒閃退或出現(xiàn)某個按鈕因貼圖缺失而消失不見的狀況就全刪。
每清理掉一個,我還將修改版RPE的界面與原版進(jìn)行比對,反復(fù)確認(rèn)沒有按鈕缺失。
全部弄完以后,整個文件夾大小減小了1.08MB!
好,到此第一步就大功告成了!
除此之外還有很多大Bug從1.0一直流傳到1.3一直不修!
比如這個下拉框超出屏幕邊緣的Bug:

最后一個選項無法正常選取
這個也算是解決起來相對比較簡單的,改一下UI.txt就可以了。
我的修改方案是:
把密度和音符種類的位置互換,再把密度與曲線類型的位置互換。
原本我是想要把曲線類型的選項直接挪到最上面的,結(jié)果修改完發(fā)現(xiàn)音符種類的下拉框?qū)蛹壉惹€類型的下拉框要高,會有遮擋。
所以最后還是選擇把它放到中間,音符種類擺最上面。
這樣一來,下拉框中的最后一個選項Bounce就能正常選取了。
這一點我覺得cmdysj做的還是很友好的,所有組件的位置大小都能讓你自己去DIY。
接下來我們來對RPE的界面做一下優(yōu)化:
2004年前后流行的擬物化界面設(shè)計風(fēng)格是真的帥到爆炸!
后來的扁平化全都是垃圾!
就這種界面設(shè)計也能叫設(shè)計???
我用Windows自帶的畫圖都能給你畫出來!
沒有陰影,沒有顏色漸變,沒有光照。
幾根簡單的線條一勾勒就算完了。
Re:Phiedit的界面同樣也是用的這個扁平化的設(shè)計風(fēng)格,就是因為這樣做起來簡單,輕松!
甚至還會被一群對美術(shù)一竅不通的人夸獎很時髦!
好在cmdysj沒有對軟件的資源文件做任何的加密,也就意味著任何人都能夠按照自己的喜好去DIY。
那正好,讓我來幫你換個好看點的皮膚吧!
黑樂譜作者們最喜歡用的超高性能midi編輯器Domino的作者Takabo,除了開發(fā)Domino以外還制作了不少別的很好用的小工具。其中有一個就是AquaMaker。

這是我之前在Takabo的主頁上隨便瞎逛時發(fā)現(xiàn)的。
正好可以用來給RPE制作全新的按鈕貼圖!
Takabo制作的軟件操作邏輯都非常直觀,比蘋果做的還要好,即使是對日文一竅不通的我也能連蒙帶猜搞定。(不是廣告?。?/p>
經(jīng)過了反反復(fù)復(fù)的修改測試,我把RPE的界面成功修改成了如下圖所示的模樣:


雖然與我認(rèn)為出色的界面設(shè)計相比還差了很大一段距離,但至少現(xiàn)在看上去已經(jīng)比原版要好了很多了!
個人能力有限,如果你能設(shè)計出更好看的那么歡迎加入XPE項目。
現(xiàn)在,我們在不進(jìn)行逆向的情況下可以做到的所有修改就已經(jīng)結(jié)束了。
cmdysj的代碼實在是寫得太TM糞了,我受不了了我IDA動了。
做了一大堆花里胡哨的新功能,但每一個都堅決不把它做到最好。
這次的這個逆向工程真的是邊搞邊笑!哈哈哈!
各位準(zhǔn)備好,正片開始?。?!
我在前段時間搞Tan90時注意到了一個奇怪的現(xiàn)象:
當(dāng)我拖動右上角的進(jìn)度條時,整個拖動的過程都很流暢,然而偏偏在我松手的一瞬間卡死不動。等好幾秒鐘才能緩過來。
與此同時,我還注意到在1.2.1中按Q,W,E,R放置音符的操作全部都是瞬間完成的。而在1.3中則要卡上好一會。
這啥情況?
這明顯不是RPE的圖形渲染出了什么問題,一定還有什么別的東西在大大拖垮RPE的性能!
此時,我想到了一種可能的情況:譜面糾錯!
為了右上角的兩條線能夠在對譜面做出修改后及時做出響應(yīng),cmdysj直接簡單粗暴地在每一次對譜面做出任何修改動作時都自動調(diào)用一次譜面糾錯函數(shù)!
好幾十萬音符的超大型塞炸譜Tan90如此頻繁地要被檢查糾錯它能不卡嗎?
于是,我打開了全網(wǎng)最強逆向工程神器:IDA。
嘗試想要把這個惡毒的譜面糾錯干掉。
然而,當(dāng)我開始以后才發(fā)現(xiàn),事情并沒有我所想的這么簡單。
由于cmdysj從1.2版本開始就不再提供對應(yīng)的PDB文件,所有的函數(shù)名稱全部都是鳥語。
又臭又長的匯編代碼完全看不懂。
一時完全不知從何下手。

但好在1.1及以前的版本中都有PDB。
我們可以先從1.1入手,找函數(shù),之后再在1.3中直接搜索代碼或字符串就可以了。
打開1.1版本的主程序以及其對應(yīng)的PDB文件后,函數(shù)名稱終于正常了。

搜索一下看看有沒有什么和譜面糾錯有關(guān)系的。
很快,我找到了這個:

可以看到,這里有檢查譜面中各種不同類型錯誤的函數(shù)。
如xy綁定,音符重疊,事件,讀譜時間等等。
隨便打開一個,按下Ctrl + X快速查找出所有調(diào)用這個函數(shù)的語句:

就是你了!點OK進(jìn)入。

好,罪魁禍?zhǔn)兹孔サ健?/p>
把這幾行語句全部替換成nop,意是就是啥都不做。

成果展示:

但這么一來,譜面糾錯就完完全全徹底沒法用了。
顯然,這并不是我們想要的。
我們希望能夠在我手動點擊右上角的刷新按鈕時進(jìn)行一次糾錯,其他時候就不要動了。
那接下來我們就需要去找有哪些語句調(diào)用了這個譜面糾錯的主函數(shù),把其他的全部刪掉,只保留下刷新按鈕。
向上滾動來到函數(shù)的開頭,并按下Ctrl + X。

好家伙!這玩意居然在整個程序中被調(diào)用了50次!??!
用JitBit簡簡單單做一個腳本批量給這50個調(diào)用語句全部加上斷點。
腳本大致內(nèi)容為:
標(biāo)簽:Begin
Ctrl + X
下箭頭
回車
F2
Esc
跳轉(zhuǎn)到標(biāo)簽:Begin
運行程序,開始測試。
我滴個乖乖!這個糾錯函數(shù)基本每點一下鼠標(biāo)就要被調(diào)用一次??!
不卡才怪!
此時點開主界面右下角的譜面糾錯,點一下刷新。
回到IDA里,看看是這50次當(dāng)中的哪一個。
按F2把它的斷點刪掉,其它地方不要動。
停止調(diào)試,回到IDA主界面。
按Ctrl + Alt + B打開斷點列表,拖動它把它固定到IDA主界面右側(cè)。
就像這樣:

接下來用JitBit簡單寫個腳本把所有打了斷點的語句全部替換成nop。
注意:由于指令長度不同,需要使用五個nop替換掉一個call語句。
腳本大致內(nèi)容如下:
標(biāo)簽:Begin
雙擊打開右側(cè)的第一個斷點
Alt
E
P
A
輸入文本:nop
回車
回車
循環(huán)4次
????下箭頭
????回車
循環(huán)結(jié)束
按下F2
跳轉(zhuǎn)到標(biāo)簽:Begin
很快,JitBit就完成了全部50次調(diào)用的修改。
測試一下,運行效率的確比以前提高了不是一點半點!
但是,我們的最終目標(biāo)是要去修改1.3,不是1.1。
接下來就讓我們來簡單觀察下這里面有沒有什么我們可以利用的好東西:
按F5查看偽代碼,可以減小代碼閱讀難度。
很快,我找到了這個:

出現(xiàn)字符串了!關(guān)鍵信息!
對我們在1.3中找代碼有很大幫助!

OK,找到你了!
點進(jìn)去,向上翻,找到函數(shù)的開頭。
把光標(biāo)挪到那里然后按Ctrl + X。
成功定位到關(guān)鍵函數(shù)!

對比一下,一摸一樣!

找到這個關(guān)鍵函數(shù)的開頭,右鍵點這里給他重命名:

這樣我們后面操作時就會容易一些。

之后對著它按Ctrl + X

驚了!整整60次調(diào)用?。?!
1.1里面才僅僅50次,1.2里55次,1.3居然給直接增加到了60次!
難怪1.3這么卡!
按照之前搞1.1的操作在1.3里再搞一次。
好了,現(xiàn)在的這個1.3簡直比1.1流暢!
不知道各位有沒有注意到即興演奏的一個大bug:
按下冒號和問好會把音符放置在同一個位置!

這個Bug我不知道各位受得了受不了,反正我是已經(jīng)要瘋了!
強迫癥表示這玩意實在看不下去?。?!
然而,接下來的難點在于我們無法通過1.1去找對應(yīng)的關(guān)鍵函數(shù),因為這個功能是1.3里才剛加入的。
這里我們可以去嘗試找一下即興演奏中用到的一些快捷鍵看能不能搜到:

確實可以!
第一個是1.3的按鍵顯示,第二個就是我們要找的了。
進(jìn)來一看代碼,給我氣笑了!

怎么前面都寫得好好的,9,10,11,到了12這邊就莫名其妙寫了個13?
只能感嘆cmdysj的代碼寫地實在是太粗心啦!
仔細(xì)看一下,這不正是出問題的那個按鍵Semicolon嘛!
把它改成12,問題解決!

之后我便開始制作XPE的廣告片。
結(jié)果,我居然在制作廣告片的過程中意外間又發(fā)現(xiàn)了RPE的第二個性能殺手?。?!
當(dāng)我在制作這里https://www.bilibili.com/video/BV1aX4y1s71R?t=51.4的性能優(yōu)化演示時,我注意到了一個很奇怪的現(xiàn)象:
用Cheat Engine把XPE減速到0.5倍速,再在軟件里把播放速度調(diào)到2.0。
理論上來說應(yīng)該與之前播放的流暢程度沒有區(qū)別,對吧?
然而,我卻發(fā)現(xiàn)這樣操作過以后播放的流暢程度卻又了很大的提升?。?!
這啥情況???
要知道,cheat engine減速是不會對軟件代碼的執(zhí)行速度產(chǎn)生任何影響的。
只有跟計時有關(guān)的語句會因為cheat engine的介入而計算出錯誤的時間以達(dá)到增減速度的效果。
此時,我想到了一種可能的情況:計劃任務(wù)。
讓程序每隔一段時間就調(diào)用一次某個特定的函數(shù)。
用Cheat Engine減速會使單位時間內(nèi)調(diào)用的次數(shù)減少。
而在軟件內(nèi)使用變速功能不會對這些計劃任務(wù)產(chǎn)生任何影響。
計劃任務(wù)是一種非常方便快捷的實現(xiàn)偽多線程的方式。
其原理在于將整個CPU分割成N多個時間片,在每個時間片中安排不同的任務(wù)。
由于幾個時間片之間輪轉(zhuǎn)的速度很快,從表面上看起來便是CPU同時在做多件事情。
我之前在AppInventor中做一個搶答器App的時候就是用的這個方法實現(xiàn)的多線程。
(注:AppInventor不支持真多線程)
C++雖然可以做真多線程,但做起來比較繁瑣,于是cmdysj就用計劃任務(wù)偷了個懶。
但是用計劃任務(wù)的方式實現(xiàn)偽多線程有一個大問題!
正常情況下,需要同時運行的幾個函數(shù)是這樣被調(diào)用的:

但如果需要處理的數(shù)據(jù)量過大的話就會出現(xiàn)接下來的場景:

前一個函數(shù)還沒運行完,后一個就要被調(diào)用。
上圖畫的是安排,到了實際執(zhí)行時就會變成如下的模樣:

這樣一來便會導(dǎo)致程序主循環(huán)被嚴(yán)重拖慢,軟件運行的流暢程度嚴(yán)重下滑!
靠逆向工程把RPE改成真多線程幾乎是不可能的。
我們只能通過修改代碼增加這些計劃任務(wù)執(zhí)行的延遲,多分配一些處理時間。
這樣便能使XPE的執(zhí)行效率再一次得到質(zhì)的飛躍!
要修改延遲,首先需要弄明白RPE是如何實現(xiàn)計劃任務(wù)的。
我注意到左側(cè)的函數(shù)列表中有不少跟一個叫cocos2d的東西有點關(guān)系:

于是隨手上網(wǎng)去搜了一下這個玩意:

哦,原來就是個游戲引擎啊。
那來讓我看看如何用cocos2d實現(xiàn)計劃任務(wù)?

原來是用的Schedule。
于是我就去代碼中搜了一下Schedule:

真的有,而且還不少!
點進(jìn)去看一下代碼:

按照我查到的schedule語句的用法,中間的是要調(diào)用的函數(shù),最后一項應(yīng)該就是用浮點數(shù)表示的時間。
拿這個hex to float轉(zhuǎn)換器轉(zhuǎn)換一下看看:https://www.h-schmidt.net/FloatConverter/IEEE754.html

臥槽!這延遲就TM短的離譜?。?!
每0.008秒執(zhí)行一次?。?!
不卡才怪!
接下來我們要做的就是修改它們的延遲。
讓這些函數(shù)在單位時間內(nèi)少執(zhí)行幾次。
在轉(zhuǎn)換器最上面的那個文本框中填入新的浮點數(shù)時間(單位為秒),并把下面轉(zhuǎn)換出來的hex復(fù)制下來。
在IDA中選中scheduler的最后一個參數(shù)的數(shù)值,按Tab鍵跳轉(zhuǎn)到在Disassembler視圖中對應(yīng)的位置。
依次點擊Edit -> Patch Program -> Assemble把剛剛復(fù)制下來的數(shù)值貼進(jìn)去修改就可以了。
嗎?
又一個新的問題來了,IDA貌似還不支持修改這個指令。

能把這個指令從exe里反編譯出來卻無法編譯修改后的指令真的令我很意外!
那看來我們只能用一些別的方式去修改了。
于是我便想到了進(jìn)入hex視圖看看能不能直接從機(jī)器語言下手把這行代碼給改掉。
打開hex視圖后先不著急改,回到Disassembler視圖。
在空白處右鍵找到Synchronize with,勾選上Hex View-1。

接下來你就會發(fā)現(xiàn)你在Disassembler中把光標(biāo)放在哪一行Hex里就會跟著高亮顯示出那行代碼所對應(yīng)的hex。


經(jīng)過一番分析,我發(fā)現(xiàn)了如下的規(guī)律:
以C7 04 24開頭,之后接上以兩個字母為單位倒序排列的浮點數(shù)時間的hex。
右鍵,然后點Edit,把這串?dāng)?shù)字修改掉。
再按右鍵,點Apply,剛剛做的修改就都成功地應(yīng)用上了。
按照同樣的方法把其它所有的scheduler全都改掉,依次點擊Edit -> Patch Program -> Apply patches to input file把剛剛我們做的修改寫入到可執(zhí)行文件里。
之后只要再簡單修改一些字符串,例如把v1.3.0 @cmdysj修改成Ex:Phiedit v4.0等,這個XPE就可以算是完工了。
感謝各位的觀看,我們下期再見。