【感謝吳銘(@淺水清流)的熱心翻譯。如果其他朋友也有不錯的原創或譯文,可以嘗試推薦給伯樂線上。】
從2009年開始”非常規程式設計技巧”成為了開發者之間的一個熱門話題,通過這個話題,我們得知了一些開發者為了按期釋出或是通過測試而”發明”的非常手段。在這裡我們將為大家分享9個(帶著硝煙味的)故事,包括(非程式設計的)其他開發行業的故事。閱讀這篇文章,可以使你陶醉於同事的奇思妙想,並且長出一起口氣——因為你不是唯一一個在壓力下掉節操的。
1. Crouching RSX,隱藏的紋理資源
Joe Valenzuela, Insomniac Games
這一招使用在了PS3上:當時在Insomniac的引擎團隊,我們希望將一些紋理資源和我們的引擎/工具一起釋出。這些資源類似於噪音紋理並且用於全域性濾鏡特效。出於一些無關緊要的原因,我們沒有直接釋出這些資源,而是將它轉為二進位制資料並且編譯到可執行檔案之中。這樣做有一個缺點,我們希望這些資源在不同的記憶體塊中(RSX可見),所以我們最終將它們複製出來但是浪費了一些記憶體。
PS3的連結工具有一個功能可以將一些段放在RSX記憶體中,但是需要佔用至少1MB的記憶體,並且在我們的需求中會浪費700KB。所以最終我們向可執行檔案中新增了一個新的資料段別名為BSS(the “bss alias” or “balias” section)。在最終釋出版中我們有3MB的BSS段,這樣我們有足夠的空間在儲存我們的紋理資源。在CRT初始化目標記憶體之前,我們執行一段程式碼,將BSS中的紋理資料拷出,然後重新將BSS段初始化為0。
不管你信不信,這段程式碼工作良好。為了適應BSS段的使用和工具的更新我們做了一些調整,不過總體來說還是很簡單。
2. 這不是你正在尋找的錯誤(Bug)
Brett Douville, LucasArts
在2002年,我們準備釋出為Sony開發的遊戲”星球大戰:絕地戰機”。一個瑣碎的TCR bug仍然沒有解決,在我們載入過關的過場動畫時,這個bug會導致控制器的模擬搖桿功能關閉以及中心控制器的紅燈熄滅。當我們更新為sony提供的庫版本時,這個bug被發現,而開發載入動畫和IOP邏輯控制器功能的程式設計師已經離職了2個月。
為了在這些我不熟悉的程式碼中快速的定位問題,我在7處程式碼可能出錯的地方插入了不同顏色的清屏程式碼,希望通過觀察控制器關閉模擬功能前的螢幕顏色來縮小錯誤程式碼的範圍(很常規的採用log排查bug,這個方法的價值比故事大)。但是當我再次測試時,bug沒有重現。
有一個古老的程式設計諺語,如果你不知道確切的原因,你就不能說已經解決bug。但是現在我們離版本釋出只剩下2-3天,而這個bug並沒有什麼大不了,所以我將所有的清屏顏色改為黑色,並將bug標誌為已經解決,然後測試了一整天。之後我們如期釋出,而TCR bug沒有來攪局。
3. (s)elf-exploitation
Jonathan Garrett, Insomniac Games
Ratchet and Clank: Up Your Arsenal是一款有著線上的名義但是很不幸缺乏線上升級功能的遊戲。
每次遊戲釋出需要下載並顯示一個終端使用者許可協議。許可協議以ASCII字串的形式儲存在靜態緩衝區,緩衝區被寫入從伺服器下載的資料,並且不會檢查緩衝區的大小。
我們利用這個隱患,在EULA下載的時候導致靜態緩衝區溢位並且覆蓋了一個全域性的變數。這個變數正好是處理網路資料的回撥函式地址。一旦覆蓋了這個變數,我們就可以傳送資料,並且跳轉到新的(被覆蓋寫入的)地址。這個地址指向了一些再下載EULA之前下載的可執行的程式碼。
有效的資料在EULA緩衝區尾部和被覆蓋的全域性變數之間,所以程式碼執行的首要工作是恢復被丟棄的資料。一旦資料恢復正常,就可以開始實際的修補工作。
一個複雜的問題是使用strcpy複製EULA文字。當strcpy發現一個值為0的位元組(一般是字串結尾)時會停止工作。而我們的字串通常包含值為0的位元組。因此我們對編譯後的程式碼編碼為不含值為0的位元組的並且有一塊精心製作的引導區進行解碼。
最終,這個手段過程如下:
- 1 傳送一塊超大的EULA
- 2 EULA緩衝區溢位,雜項資料,覆蓋回撥函式指標
- 3 傳送資料包觸發處理程式
- 4 遊戲跳轉到引導程式碼地址
- 5 解碼有效資料
- 6 下載有效資料和恢復雜項資料
- 7 執行補丁
要點:釋出的遊戲包含線上升級和不要使用不安全的strcpy。Include patching code in your shipped game, and don’t use unbounded strcpy.
4. Jamming the cartridge 塞滿磁帶
Michael A. Carr-Robb-John, Monolith Productions
在1993年,我將Desert Strike從16位的主機移植移植到8位的主機。遊戲所需要的磁帶大小約為12K,並且更大容量的磁帶超出了預算。現在12K聽起來非常小,但是在當時是筆不小的預算。在開發過程中,所有的圖形和聲音資源都被我壓縮到了極限,唯一的可以壓縮的只有部分程式碼。在那個時候,遊戲一般採用的是組合語言,我們採用的是Z80彙編,所以我只有一個選擇。我花了一週的時間查詢了冗餘程式碼,並且用記憶體佔用更小的方式重寫(一般會消耗更多CPU)。
當我完成之後,遊戲安裝到磁帶只有98B的剩餘空間!遊戲被燒錄成ROM經過幾天的測試之後,提交到世嘉進行認證。不幸的是第一次測試沒有通過,之後修復bug很快用完了這98B。當我們釋出的時候,剩餘空間只剩下6B。
5. The Dalton Allocator
Jonathan Adamczewski, Insomniac Games
在一個快要結束的專案,我們發現在長時間遊戲之後,觸發過場動畫無法正常播放影片。因為我們的記憶體分配器不能可靠分配足夠記憶體,以供影片全屏播放。
我們需要找到一種方法以確保我們可以得到足夠的記憶體。但是已經到了專案的晚期,我們沒有預留足夠的記憶體供影片播放使用,而且採用碎片整理堆的風險太大,從其他的模組得到剩餘的記憶體也不切實際。
可以明確,影片播放的時候其他很多模組處於空閒狀態,所以我們可以考慮從其他模組得到記憶體。然而這些潛在的記憶體來源要麼太小,要麼同樣受困於碎片問題。GPU有大量的記憶體空間,但是因為一些原因影片播放器不能直接使用它做緩衝區,因此我們需要一些其它來源的地址。
轉到另一個問題,我們遊戲為主要資源分配記憶體的一個特性模式被發現了,當遊戲開始的時候我們從磁碟載入資源,這些資源在記憶體中不會被修改。其中一些資源非常大。而影片播放的時候它們都不會被使用。
所以我們最終的做法。我們選擇了遊戲中的一個英雄(名為Dalton)最大的一套動畫。過場動畫觸發時,在動畫系統關閉之後將這套資源拷貝到GPU的記憶體中,然後將資源佔用的記憶體給影片播放系統使用。在影片播放結束後,動畫資源被複制回原來的記憶體中,動畫系統重新開始,遊戲正常執行就像什麼都沒有發生一樣。實現的過程相當簡單,雖然跨越了數幀以確保所涉及的系統的每一個步驟都安全的同步。
影片系統的記憶體被描述為從”the Dalton Allocator”分配,在動畫資源的記憶體被強佔之後。
(我發現這種事情在工作室已經有一些歷史,在專案早期沒有預留足夠的空間,所以在GPU記憶體中隨意塞入幾個東西)
6. Certification headache
Michael Carr-Robb-John, Monolith Productions
任何一個為遊戲機開發過遊戲的人都曾經為通過認證頭疼過。大多數情況下,認證的標準一般只是一個慣例並且是良好的做法,但是總有一些標準會成為開發商最頭疼的問題。其中一個這樣的標準就是,我們必須保證在使用者選擇開始遊戲後4秒內顯示第一幀畫面。如果你的程式很大,在進入主選單前至少要載入2-3秒,而此後你還要載入一大堆的聲音和資原始檔。
我的遊戲在使用者開始遊戲後需要26秒,所以這對我來說是個大問題。我的第一個優化是找出主選單所必需的資源,其他資源都延後到需要時載入。出乎意料,這樣做優化了9秒的時間。在做了許多其它優化後,載入時間減少到10秒以內,但我沒法再繼續優化下去。在和另一個工程師討論之後,我終於找到了最好的辦法。
這個遊戲機要求在進入主選單前必須播放2幀特定的畫面,而且使用者不可以跳過這2幀畫面。這樣就有了一個辦法,我們最初只載入這2幀畫面,在使用者看這2幀畫面時再載入主選單所需要的資源,讓我們來下……balabala……這次只畫了5.5秒。
很不幸,這還是不能達到標準。所以最終我們不得不申請豁免,當然我們通過了(因為離標準已經很近了)。
7. Painting sounds
Edward J. Douglas, Flying Helmet Games
我長期為一個賽車系列遊戲從事動畫藝術工作。我們的遊戲場景是一些網格和動作序列的組合。在我們的續作中,我們的野心越來越大,但是我們的技術更新比較慢。
我們的動畫製作過程是,是從一個特效軟體製作基礎的動畫,然後匯入到三維動畫程式中調整動作的時間和位置,接著重新匯出到遊戲的動作工具加入物理和引擎的模擬資訊,大量遊戲資料被捕獲,包括氣體和來至控制器的資訊,並儲存在3D檔案的後設資料中,然後在遊戲中再現。當時的想法是通過這個(讀取對應3D檔案中的音效資料)驅動遊戲的音效系統。
在幾個續作之後問題又來了,在混合了手工捕捉的記錄和汽車動作後,我們的動畫變的非常複雜。以前老的通過使用3D檔案的後設資料來驅動我們汽車的音訊樣本的做法行不通了——因為資料可能不存在。音效團隊不可能像提交動畫一樣提交音訊,因為在什麼場景使用什麼汽車取決於玩家的選擇,所以音訊需要能動態選擇。但是,我們遊戲的音效已經贏得了無數獎項,特別是汽車發動機的聲音,所以我們覺得繼續使用它工作。
公測即將開始,事情變的瘋狂起來,但是由我們的過場動畫,音效,AI組成的瘋狂而又才華橫溢的團隊找到了解決的辦法。油門和剎車在3D場景中採用一個浮動的立方體表示。如果一個藝術家加入,並且使用類似3D Max的編輯軟體繪製一些曲線,他們可以畫出期望的發動機聲音曲線。音訊團隊的幾個成員臨時學習3D Max的使用,並且使用他們的直覺來判斷什麼轉速模式採用什麼樣的聲音曲線,然後趕在釋出前,匯入到所有場景。最終的音效聽起來不錯,但是最後採用的手段讓我們明白,我們需要更新技術保證續作能有更好的表現。
也是隻有我是這麼想的,在那個版本之後,我離開了工作室。幾年以後,我遇到了一個做音效的傢伙,在我之後加入了團隊。不久後,我就意識到,他們並沒有更新技術,他就是那個為最新續作繪製”發動機聲音”的傢伙。
8. And one for good luck
Richard Morwood
我有一個背景紋理列表,在遊戲滾動到對應場景時,我編碼顯示對應的背景。但是有一個背景圖片被跳過,在花費了大量的時間除錯之後,我仍然沒有找到原因。離截止時間只剩下5天了,所以我插入了一個額外的紋理到列表中。Ta-da! 沒有背景被跳過了:)
9. HR hacks
Ben Burbank
當我在一家很大的公司工作時,有一個員工想通了,推動他職業發展的最佳方式就是儘可能多的給其他員工的績效負面評價,這將提高他的績效排名,並且進而帶來更高的獎金和股權分配。這樣做並不容易,因為你需要確保被負面評價的是其他經理的下屬,這樣沒有人可以識破你的詭計。我避免這種惡性迴圈的辦法是,去一個小地方工作,好棒。
Honorable mention: Nice save
Chris Pruett, Robot Invader
[Editor’s note: This isn’t, strictly speaking, a dirty game dev trick — but we figured it’s a handy way to use job skills for real-world problems. Also, it’s a sweet story.(一個浪漫的故事)]
我的妻子很少會玩遊戲,但是勇者鬥巨龍系列,她在童年就開始著迷。幾年前,她開始在我的老PlayStation上玩勇者鬥巨龍VII,大約80小時候(據我所知,已經完成主線任務的3/4)後,她發現,她的儲存檔案被損壞。在繼續遊戲的選單中能出現,但是已經變灰無法選擇。她變得絕望,生氣,發誓再也不玩遊戲。
我發現一個裝置DexDrive可以讀取PS儲存卡在PC上使用,在eBay上賣$15。我打算試試看能不能修復存檔,但是我沒有告訴我妻子,因為我不想給她希望再讓她失望,因為我實際上覺得修復是不可能的。據推測,資料損壞時無法挽回的,但是另一方面,我猜它也許沒有被損壞。
使用DexDrive,我將被損壞的檔案從PS儲存卡讀取到PC上,然後使用十六進位制編輯器開啟它。我最終把它列印出來,並且使用熒光筆標記了16進位制。儘管PS1儲存在一個8KB的塊中,但是8KB的16進位制數列印了不少頁。在一個PS1模擬器的作者寫的非官方規範中,我瞭解了資料的主要結構:頭部,圖示,最後資料本身。比較不幸,解碼原始的遊戲資料非常有挑戰性,幾天之後,我確定這比預計的工作量大不少。
最終,我把工作集中在資料頭部。由於圖示記錄(這是在一個從檔案頂部開始的偏移值,並且易於儲存為畫素資料)的位置,通過頭部可以告訴程式開始和結束的位置。如果繼續遊戲選單顯示存檔被破壞,也許是頭部的資料被損壞。我測試了一下使用我從網下下載的資料頭部,然後將它覆蓋到被損壞的存檔中。然後我將存檔儲存到儲存卡,重新載入它。
不可思議,它開始重新正常工作。繼續遊戲選單顯示了其它的存檔,但是一旦載入完,她的存檔完全恢復。從訂購DexDrive到修復完成,整個花開我3周的時間。那天晚上,我啟動遊戲,並向她展示了她奇怪的存檔命名。她載入完,很驚訝的發現,她丟失的進度,角色,等級全部都回來了。她非常的興奮,但是在我們討論之前,她開始一個新的副本了。