After the release of wowSelect 1.0.0 beta, some reported that the program would just freeze after launching WoW client. My first thought was that there’s something wrong in the debugging loop, causing wowSelect to wait for a specific event that never came. However, I had already tested on both Windows 7 and Vista and they both worked well, as expected. So this led me to that the reported bugged environment itself, which was Windows XP in the case, had the possibility to be responsible for this.
Before going further, I think it’s better to explain how wowSelect works in details.
Many custom WoW launchers you can find on the Internet are designed to enable players to connect to private servers, and they do so by mainly modifying the file realmlist.wtf inside Data folder. However, if your Windows is UAC-enabled, this requires you to authorize the launcher to run as Administrator. Actually, the Windows team admit that they design the UAC so that developers who want to get their programs running on those newer Windows must be cautious about the privilege and security issues that are for a very long time ignored by them and Microsoft. So a good program must use the minimal possible privilege to accomplish its job – it’s just silly to be annoyed by UAC pop-ups every time you want to play a game or check e-mails on Gmail as they have no reason to alter any system settings nor important files.
That’s why wowSelect uses debug registers and debugging API to hook CreateFileA() and dynamically modifies the parameter passed to it. wowSelect first creates a temporary file and fills its content that specifies a desired server and then uses the hooking technique to break on the CreateFileA(“realmlist.wtf”) invocation and modifies its parameter to the path of our previously created temporary file. As the temporary file can be created anywhere and the rest of the process is done totally in memory, there is no need to alter any system file and thus wowSelect does not trigger a UAC pop-up dialog.
To install a hook, wowSelect tries to set up debug registers, so the WoW process will be interrupted at the address of CreateFileA() in kernel32.dll, just prior to the very first instruction of that Win32 API function being executed. Later on, after finishing some work, wowSelect sets Resume Flag (RF) in the EFLAGS register in the client’s only thread by SetThreadContext(), this makes CPU not to issue an exception at the next instruction when returned from interruption, which is the first instruction of CreateFileA(). So the client continues, unaware of those things just happened.
While testing version 1.0.0 beta on Windows XP, I found that setting RF, combined with a subsequent SetThreadContext() call, had no effects on the interrupted instruction; that is, the WoW process is stuck in the same instruction and never continues ever since the first interruption. If you look into the MSDN document for SetThreadContext(), it says that some bits in the CPU status register are ignored by the function because they must be maintained only by the operating system. So I guess those bits include RF in Windows XP while they exclude it in later versions.
Of course, using hardware breakpoints is not the only way to hook a function. In fact, it’s rare used in this way. But for the purpose and application of wowSelect, it’s easier, more effective, and more elegant than many other methods. Because the RF couldn’t be used on Windows XP, and 2000, according to threads on many forums, I had to write some extra code and use the Trap Flag (TF) in EFLAGS instead, which works on Windows XP and is said to be the exact method mainstream debuggers use when dealing with hardware breakpoints.
Now, finally, wowSelect 1.0.0 is released.
Unfortunately, if one trys to use wowSelect on WoW 4.0.x clients, it will definitely freeze again. This is because 4.0.x clients uses CreateFileW(), a Unicode counterpart of CreateFileA(), to load server settings. I consider it a minor issue because the private server communities are yet to develop any acceptable 4.x server. Those who claim themselves 4.0.6 test (privately, for sure) servers will ask you to use a big custom launcher that patches the WoW client program in a large scale. The whole process is much more like a downgrade, in fact, since their servers run a 3.5.5a-like protocol and therefore they must make your client use the same old protocol as well, which enables you to login with a 4.0.x client but unable to enjoy Cataclysm game features. I think it’s better to wait for MaNGOS or any other project to release a stable and usable 4.x server. So I’m not in a hurry to make wowSelect capable with 4.0.x clients anyway. (You can still patch wowSelect on your own. With or without its source code, it’s all up to you!)
.
在 wowSelect 1.0.0 beta 釋出之後,我收到了一些關於它在嘗試啟動魔獸主程式之後就會當掉的回報。我第一個念頭就是 debugging 迴圈裡面發生了甚麼事情,使得它一直在無止盡地等待某個永遠都不會出現的除錯事件。不過我在手上的 Windows 7 與 Vista 都能夠如同預期一般地正常運作,這讓我開始懷疑造成問題的元凶會不會是回報者所使用的 Windows XP。
在故事繼續之前,我想還是先稍加解釋一下 wowSelect 的運作原理。
網路上充斥著許多客製的魔獸啟動器,它們主要被用來讓魔獸主程式能夠連到世界各地的私人伺服器。魔獸主程式用來存放連線伺服器資訊的是 Data 目錄裡面的 realmlist.wtf 檔案,這些啟動器會先更改這個檔案的內容,然後幫你執行遊戲主程式。然而如果你的 Windows 啟動了 UAC (使用者存取控制,就是那個經常在執行程式之前會跳出來要求更多權限的對話視窗),更改安裝在 Program Files 目錄下的檔案之前必須要通過 UAC 提升成系統管理者權限。事實上,微軟的開發團隊也承認之所以將 UAC 設計成一個很煩的機制,是為了讓使用 Windows 的程式開發者能注意長年以來被眾多開發者和微軟本身所遺忘的權限與安全性問題。因此一個好的程式應該要盡可能使用最小的權限去完成它的工作 ── 使用者不能忍受每次啟動遊戲或是開 GMail 收信的時候都要被煩一次,因為它們根本不該動到系統設定。
這便是為什麼 wowSelect 使用了除錯暫存器(debug register)與除錯 API 來 hook CreateFileA() 這個 Win32 API 以及動態更改傳給它的參數。wowSelect 會先產生一個暫存檔,並寫入可以被魔獸主程式識別的伺服器相關設定內容。接著啟動魔獸主程式,然後透過 hook 等待主程式呼叫 CreateFileA(“realmlist.wtf”),因為這正是它要載入設定檔的時間點。就在這個關鍵的時候,我們巧妙地將原本的參數 “realmlist.wtf” 改成方才所建立的暫存檔路徑,而該檔案裡面已由 wowSelect 設定了一個我們希望連上的伺服器(可以是其他國家的官服或是任何私服)。正由於暫存檔可以存在於任何使用者可寫入的路徑,加上剩下來的事情又全部都在記憶體當中進行,所以 wowSelect 不需要透過 UAC 請求權限就能完成所有工作。
為了安設 hook,wowSelect 會設定相關的除錯暫存器,使得魔獸主程式呼叫 kernel32.dll 裡的 CreateFileA() 函數時,CPU 讓 EIP 暫存器指向該函數的第一個指令(instruction)的瞬間會引發中斷(interrupt)。接著在完成一些必要工作之後,wowSelect 會透過 SetThreadContext() 設定 EFLAGS 暫存器當中的 Resume Flag (RF),這個旗標(flag)將控制 CPU 自中斷處理程序返回之後,於緊接著的第一個指令內暫時不引發除錯相關功能的例外(exception)。魔獸主程式因此得以繼續執行,而且對於剛才在中斷過程中發生的一切事情毫無知覺。
在 Windows XP 上測試 1.0.0 beta 版的問題時,我發現使用 SetThreadContext() 來設定 RF 根本沒有作用。也就是說,會當掉完全是因為一旦某個中斷點被觸發,CPU 不知道我們在處裡完這個中斷之後希望它暫時不要在同一個指令上丟出相同的例外(因為我們已經處裡完),而很開心地持續產生中斷。如果你查一下 MSDN 對於 SetThreadContext() 的說明文件,就會發現它只告訴你「有些」CPU 狀態暫存器上的旗標會被忽略,因為那些旗標應該要由作業系統來管理。我只好推測 Windows XP 會把 RF 算在「那些」會被忽略的旗標當中,而之後的 Windows 則不會。
當然啦,使用硬體中斷並不是唯一可以用來 hook 的方法,而事實上這個方法也鮮少被如此使用。但是若站在 wowSelect 設計目標與架構的觀點來考慮,這無疑是個非常簡單方便、有效以及優雅的方法。既然 RF 無法在 Windows XP 當中起作用(根據諸多討論版上的文章,RF 在 Windows 2000 當中也是無法使用的),我只好多寫幾行程式碼改用 Trap Flag (TF) 和一些額外的工作來達成原本的目標。由於在我的 Windows XP 環境裡面,這個新方法可以正常地運作,而且 TF 也被認為是主流除錯器在實作硬體中斷當中所用來讓程式繼續執行而使用的旗標,我就姑且這樣視它為一個可靠的夥伴了。
就這樣,現在 wowSelect 1.0.0 (終於!)完成並釋出了。
然而還有一件壞消息沒告訴你:很不幸地,如果你嘗試 wowSelect 在 4.0.x 版本的魔獸主程式上,它絕對會當給你看。這是因為大災變讓主程式改用了 CreateFileW() 來載入伺服器設定檔,這個函數是對應於 CreateFileA() 的 Unicode 版本。不過我把它視為次要的問題,因為直到目前,私服開發社群還沒有辦法完成一個可接受的 4.x 伺服器軟體。你也許能在網路上找到一些自稱 4.0.6 測試伺服器(當然是「私人」測試),不過它們都要求使用者使用一個很肥的客製啟動器才能連上,而那個啟動器會對你的魔獸主程式本身進行非常大幅度的修改。事實上這個大修改可被視為一種大降級 ── 這些私服本身使用了 3.5.5a 版本的通訊協定(也許有一些小小的修改),所以它們必須讓你的魔獸客戶端程式也能使用這套舊通訊協定才能進行遊戲。經過大降級之後,你的 4.0.x 魔獸主程式只使用 3.5.5a 就已經存在的功能,雖然這能讓你連上那些私服,卻無法享受大災變之後才新增的遊戲內容。因此我個人覺得你應該等到 MaNGOS 或是任何其他私服軟體釋出穩定且可用的 4.x 伺服器軟體之後,再考慮去大災變的私服上玩。由此可知我個人不太急於加入對 4.x 魔獸主程式的支援,然而你還是能拿 wowSelect 去新增那些支援與功能(無論你要改原始程式碼還是二進位執行檔都可以)!