對于有些喜歡修改源代碼的人,這里是一個指南的開端。我想到什么說什么,第一篇說說如何修改源代碼讓你用TM登錄。
你不想使用QQ登錄,想使用TM登錄么?通過修改源代碼,是很簡單的。
其實TM和QQ的協議是一樣的,雖然有些功能TM有QQ沒有,但是基本上都不是服務器的限制。舉個例子說,TM里面有個“隱身對某人可見”功能,命令是0x0024,雖然QQ里面沒有這個功能,但是你如果真的通過QQ發送這個命令,服務器目前是不會拒絕的。
PS: 所以我一直覺得TM不過是界面改改罷了,里面的東西,照舊。
我們看一下QQ.java, 有這么一個常量
public static final char QQ_CLIENT = QQ_CLIENT_0E1B;
你把他改成
public static final char QQ_CLIENT = 0x0F0A;
然后你再運行,LumaQQ就成了LumaTM了
基本上啥也沒干,是不是。QQ_CLIENT是版本標識,0x0F0A表示的是TM 2006新春版。TM和QQ基本上就這點差別。也許夸張了點。
你怎么知道修改了這個常量就是TM了?假設你有兩個號碼A和B,你用騰訊TM把A設成隱身對B可見。然后嘗試用LumaQQ登錄A,看看修改前后在B的那邊有沒有區別,就知道了。
但是LumaTM還是QQ界面?這...,這不指南才開始嗎?哪有空把界面怎么改都一次說全了。
你解析了一個未知的包,不知道怎么添加到JQL么?如果你有這樣的疑問,我給你一些指南
一般來說,你要完成以下任務:
1. 添加相應的命令常量到QQ.java
2. 根據協議族不同,繼承不同的基類。以基本協議族為例,繼承BasicOutPacket,創建一個輸出包類。再繼承BasicInPacket,創建一個輸入包類。你可以看看現有的包,抄一個過來,然后修改putBody或者parseBody就可以了。
3. 根據協議族不同,找到不同的parser,以基本協議族為例,看看BasicParser.java,他負責解析所有基本協議族的包,把你的包加到parseIncoming和parseOutcoming的大switch里面吧
4. 你的包是否觸發一些事件?如果是,修改QQEvent.java,添加你自己的事件常量。千萬不要和其他的事件常量沖突了,我在里面標明了下一個可用的事件ID。
5. 根據協議族不同,找到不同的包事件處理器。以基本協議族為例,看看BasicFamilyProcessor.java,那個packetArrived的大switch里面。加上你自己的代碼,觸發你的事件吧。
6. 找到QQClient.java,添加一個方便的方法可以發送你的包,不加其實沒事,但是加了好些,上層調用起來方便。
還好步驟不算很多,Just do it!
想把圖標都換換?這個簡單
edu.tsinghua.lumaqq.resource.icon,程序的圖標
edu.tsinghua.lumaqq.resource.head,用戶頭像
edu.tsinghua.lumaqq.resource.smallhead,用戶小頭像
edu.tsinghua.lumaqq.resource.clusterhead,群頭像
edu.tsinghua.lumaqq.resource.face,缺省表情
edu.tsinghua.lumaqq.resource.image,一些比較大的圖片,logo,背景什么的
看哪個不順眼,換吧。但是文件名和圖片尺寸不要亂變。
你嫌LumaQQ的機器人實現太簡單么?是簡單了點,我只處理了普通消息,你想讓機器人也回復群消息,也回復手機短信,等等。
不麻煩,稍微改改QQClient.java吧,找找qqEvent方法,有這么一塊
case QQEvent.QQ_RECEIVE_NORMAL_IM:
processNormalIM(e);
break;
processNormalIM很簡單:
// 先返回確認
processReceiveIM(e);
// 得到消息包
ReceiveIMPacket packet = (ReceiveIMPacket)e.getSource();
doRobot(packet);
so,把下面加到qqEvent里面
case QQEvent.QQ_RECEIVE_CLUSTER_IM:
processClusterIM(e);
break;
再加一個processClusterIM方法:
// 先返回確認
processReceiveIM(e);
// 得到消息包
ReceiveIMPacket packet = (ReceiveIMPacket)e.getSource();
doRobot(packet);
這樣就可以讓robot也收到群消息了,剩下的事,是你的Robot實現的事。doRobot方法里面只發送普通消息,所以要改一下,具體就不說了
想改變整個程序的樣式么? 工作有點多
我沒有實現什么皮膚,只是簡單的做了一些程序邊框的修飾,主要的工作都是在BorderStyler.java里面完成的。
BorderStyler
的工作就是你傳一個shell進去,它給shell加上邊框,標題條,還有最大最小之類的按鈕,當然還要添加一些事件監聽器。你要改也可以,不過我覺得基
本上BorderStyler沒有太多需要改的。你可以換換顏色,換換那些按鈕的圖標,這樣界面風格就可以變了。
大部分顏色都定義在Colors.java里面,你可以改。
最大化最小化按鈕的圖片在指南(2)里面有介紹,還有一些背景圖,也可以改了以便和按鈕的色調配合
光改了邊框恐怕還不行,其他控件的顏色方案也得改改吧,比如好友列表的顏色方案。
如果有些控件用到的顏色沒有在Colors里面定義,那你得稍微改改控件的代碼。
記得我在指南(2)里面說過的,改圖片的時候不要亂改圖片大小。
01-17版本改寫了聊天記錄導出的架構,稍微靈活了一些,不過實現的也不是太好,意思一下就是了啦
我缺省做了兩個簡單的聊天記錄導出模板,文本的和HTML的,如果你想添加自己的模板,或者修改現有的模板,可以看看LumaQQ_template工程,這是一個剛創建的新工程,為了正確的使用這個工程,你需要裝EMF,我用了JET來做模板
template下面就是jet的模板定義,這個工程文件不多,除了RecordExporterFactory是手寫的,其他的代碼都是從jet模板生成的。
RecordExporterFactory很簡單,隨便看看也就明白了,如果你想加個自己的模板上去,照著樣子加點代碼,然后寫一下自己的模板文件,然后執行build.xml的jar目標,然后把jar拷貝到LumaQQ_2005的lib里面,然后沒有了
實現的不是太強,夠用就行。對JET感興趣的,安裝完EMF之后,eclipse的幫助里面會有兩篇JET的文章。Eclipse主頁也有。
Windows下面沒有熱鍵,等著你們來實現
其實Windows下面實現熱鍵很簡單,比linux簡單。有人愿意寫就寫吧,這個我接受你的contribution,并且會加你到About對話框的,當然你實現的要有質量。
自然了,這一塊不得不用JNI,接口是很簡單的,就三個方法,具體去看看edu.tsinghua.lumaqq.hotkey.linux.edu_tsinghua_lumaqq_hotkey_KeyBinder.h文件,三個方法的原型在那里。
簡單說一下這三個方法:
init: 初始化
bind: 綁定一個熱鍵,熱鍵用字符串的形式描述,比如Z,綁定成功返回true,否則返回false
unbind: 取消綁定,參數和bind一樣,沒有返回值。
接口很簡單,能用就行。怎么實現熱鍵我是指導不了你了,反正Windows就是鉤子嘛
LumaQQ有個Debugger,還是很好玩的吧?你有沒有想自己寫一個debugger UI,或者讓LumaQQ的debugger功能更強?
Debug的支持是嵌入在JQL協議層的,而界面怎么實現是留給你去完成的。所以這部分代碼分成兩個部分:
1. jql_protocol: edu.tsinghua.lumaqq.qq.debug包: Debug的核心支持
2. LumaQQ: edu.tsinghua.lumaqq.ui.debug包: LumaQQ自帶的debugger UI實現
看起來很牛B其實debug功能很有限,也就是讓你看看收發了些什么包而已吧。如果你還想要監控核心層其他的活動,那這個就要你去擴展了,不過我覺得能看看包也就行了,其他的功能,因為我自己沒這個需求,自然就沒做。
Debug
在核心層的支持是可以通過DebugSwitch.java來切換的,唯一增加的開銷是一個if檢查,所以這點我還是比較滿意的。核心層在開通了
Debug功能后,會把所有的收到和發送的包都傳給你,當然你必須要添加個IDebugListener才能收到這些包。現在找到
edu.tsinghua.lumaqq.qq包,查看Packet.java和OutPacket.java,你可以發現一些Debug功能的相關代
碼,這是為什么所有的包都可以被監視的原因。
至于UI的實現,那可以很靈活,具體的就看主頁文檔,看看缺省的調試器是怎么做的。
對于寫界面來說,很大的功夫都花在了寫組件上。為了界面盡量好看點,我寫了一些自繪的組件。之所以沒去實現皮膚,那是因為太繁瑣了,沒必要。
在edu.tsinghua.lumaqq.widgets下面,都是LumaQQ用到的一些組件。這些組件你可以直接拿去用。
edu.tsinghua.lumaqq.widgets: 一些尚未歸類的組件,主要是表情頭像選擇的那個窗口: ImageSelector.java。通過實現IImageSelectorAdvisor接口,可以指定窗口的一些樣式和可選擇的圖片內容。
edu.tsinghua.lumaqq.widgets.mac:
這下面目前只有一個組件,Ring.java。從包名來看,這下面的組件是一些Mac風格的組件,如果你用過Mac,也許你對Mac的那個轉來轉去的
Busy指示器有點印象,Ring就是這個玩意。Ring.java負責處理通用的邏輯,轉圈的方式是可以擴展的。缺省的實現是圓形的,如果你想要個其他
形狀的,比如說,正方形;那么擴展IBorderPainter和ISignPainter就可以了。具體可以參考缺省的圓形實現。
edu.tsinghua.lumaqq.widgets.menu:
這里面是一個自定義的菜單實現,沒辦法的產物。在Linux下面窗口置頂時,菜單出不來,只好自己寫了一個簡單菜單的實現。優點是想怎么畫就怎么畫,美觀
有所提高。缺點是帶來了一個bug,不點一下窗口其他地方菜單不會消息,很難處理,現在也沒解決,不過想來想去,這個bug不是太嚴重,所以也就忍了。
edu.tsinghua.lumaqq.widgets.qstyle:
看名字可知,這是QQ樣式的組件。QQ什么樣式?比如QQ那個好友列表,QQ的button,等等。最主要的是QQ的好友列表,為了獲得和QQ一樣的視覺
效果,這個好友列表是最重要的部分。首先windows不提供這樣的控件,SWT也沒有,只好自己寫。在2004之前,用的叫Shutter,由于速度上
不行,后來改寫了,叫做Blind,這兩個單詞都是百葉窗的意思。Shutter已經拋棄了,就不提了。稍微說一下Blind。Blind實際上是一個組
合組件,并不是自繪的。Blind上面的按鈕是Slat,實際的內容區域是Composite子類。在LumaQQ里面,Blind里面的內容是
QTree組件,QTree是自繪的,它有QItem組成,QTreeViewer呢,是QTree的MVC封裝。主要的重繪工作在QItem里面,一個
Item(形象一點說,一個好友)分成了很多部分,好友的頭像是主圖標,好友的昵稱是文本,好友離開的時候,頭像的右下有個小圖標,這叫裝飾,好友如果是
綁定手機用戶,頭像旁邊會有手機圖標,這叫附件,在群里面,還可以看到群下面有組織,可以收起展開,圖標的前面會有加號減號標明是否展開,這叫前綴。是不
是有點暈了。QItem從QTree哪里得到一個重繪的起始位置,它自己負責計算出裝飾,附件,前綴,文本的位置,然后重畫。為了支持動畫,提供了
IEffect接口讓用戶可以自定義重畫的行為,可以參考IEffect的實現類查看具體的實現。Slat呢,就是QQ樣式的button,也是自繪的,
比較簡單。LevelBar是一個等級條,用來顯示好友的等級,缺省使用太遠月亮星星來表示,圖標是可以換的,提供了API設置。Bubble是一個
MSN那樣的冒泡窗口的簡單實現,還沒有在LumaQQ里面用到,DieAway呢,是一個淡入淡出的窗口,QQ上線提示那樣的,不過也沒有用到。
By luma at 01/25/2006 - 16:51 | 發表評論 | 更多 | 435 reads
2005的結束
這
幾天想想還有什么可以做的,想不太出來,除了文件傳輸這一塊。Bug報告的數量緩慢,難道bug越來越少?Eclipse
3.2猶抱琵琶,何時才能讓我體驗新特性,提交了幾個Eclipse
bug現在也不解,真難等哪。所以,還是結束吧,繼續開始下一個Buggy的版本吧。
雖然讓我無語的人不少,還是要感謝大家對我的支持。每個星期10多萬的點擊數也不少,可惜不是點廣告的次數,哈,肯定是比不上如來神掌威風的時期了,回想起來還真是好玩捏。
很
幸運,一些經典bug得到了解決,使2005的穩定性超越了2004T,對這點我很滿意。2006的任務是代碼重整和文件傳輸,自定義頭像和表情,還有
MSN集成。說任務是言重了,這只是我的平臺,是體驗Eclipse新特性的工具,是我的玩具,也是各位的玩具,抱著輕松的心情研究使用它吧。
如來神掌,終究是浮云,QQ,遲早是浮云。一切就不要當真了,沒有什么是我承諾給你的,也沒有什么是你肯定會獲得的,除了那些源代碼。
LumaQQ里面用到了一些Wizard,比如在搜索的時候,在創建群的時候。JFace是帶了一個Wizard框架的,用的就是它的,只不過稍微改了一下。
查看edu.tsinghua.lumaqq.ui.wizard,看到IModelBasedWizard.java,這里對IWizard做了兩個擴展,第一個是采用一個model對象來保存wizard中的信息,所以,你看到了,MVC模式是無處不在的。
第
二個擴展是加了個preNext,用來在點Next按鈕之前做一些事情,這個需求源自對QQ的搜索功能的調查。QQ搜索的時候,有時候下一步并不是到下一
個page,而是打開一個瀏覽器去網頁上找了。JFace的wizard框架似乎是不能這樣做,所以我加了一個preNext方法。為了讓這個方法能夠被
觸發,使用了WizardWindow類繼承WizardDialog以便能插入preNext方法,WizardWindow還有一個目的:使
Wizard窗口成為獨立窗口,而不是對話框(如果是對話框的話,主窗口最小化,它也會不見,所以要改成獨立窗口的),這個主要是重載
getParentShell方法,讓它返回null,雖然這個方法不是很推薦,但是似乎簡單又方便。
其他的內容就沒有什么奇怪了,按照JFace的標準框架走下來就可以了。可能有人覺得wizard那些按鈕都是英文的不爽,這個可以通過修改jface的jar包里面的properties文件來改,你可以試試。
QQ的協議非常龐大,怎么統一的描述包的結構,是一個很麻煩的問題。騰訊的包是不是有個統一的準則,不清楚,當然從設計上來說,統一的當然好。
以前以為QQ的包就那么多,設計的簡單化了。后來功能越加越多,就接觸了一些沒見過的包,發現類結構描述不了,所以只好改寫,目前的結構是一個4層的模型,真的夠用嗎?目前來說還湊合
首先我們得了解一下QQ那么多包的共同點和不同點,才好了解JQL中的類結構。
1. 目前看來,QQ的包有包頭,包體,包尾之分,但是包尾在某些協議族里面是個可選的玩意兒
2. 在某些協議族里面,包格式又有TCP和UDP之分,有些沒有。抽象一點來說呢,UDP是簡單的,隨便定義什么格式都可以滿足要求,TCP是連續的數據流,在連續的數據流里面解析包,就需要有包長度的描述。
3. 包體有全加密,不加密和部分加密的區別
4. 包有輸入和輸出包之分,不過在有些協議族里面這個區別不明顯,甚至可以說輸入和輸出包是一樣的格式
為了能夠描述現有的包,并且能夠為未知的新包提供兼容,目前類的層次有這樣4層,相關源代碼請查看edu.tsinghua.lumaqq.qq.packets包和子包
1.
一個頂層的基類Packet。它提供最基本的描述和很多抽象的方法。比如一些通用的字段,還有一些通用的方法,但是這個基類的大部分內容還是抽象方法,留
待子類實現。Packet最重要的地方在于提供了一個基本的模型,就是把包的構造和解析過程分成了頭,體,尾三部分。你可以找到putHead,
putBody, putTail方法用于構造一個包,可以找到parseHead, parseBody, parseTail方法用于解析一個包。
2.
兩個Packet的子類InPacket和OutPacket提供對輸入和輸出包的最基本封裝。Packet類是通用的模型,其不涉及輸入和輸出包有什么
不同,所以我們需要這兩個子類來提供更具體的描述。InPacket很簡單,沒什么太多內容,因為解析包的過程是通用的,封裝在了Packet中,所以
InPacket沒有多少事做。主要是OutPacket多了不少專用于輸出包的方法,比如重發次數,超時時間等等,還封裝了輸出包的填充過程。查看
OutPacket的fill方法,可以看到輸出包的生成過程。
在對網絡硬盤協議的分析中,發現有必要對JQL進行重構,以實現更靈活的協議建模和調試
主要改變在
1. 增加了PortPolicy類,隔離網絡收發和包解析功能,簡化了Port和協議之間的綁定操作
2. 簡化了IParser接口,刪除了很多不必要的方法,更加簡潔
3. 細分調試包對象,使其對應于特定協議族
4. 由于PortPolicy的引入,導致了其他一些類的相應修改,比較瑣碎,不提了
5. 細分超時事件,使其對應于特定協議族
6. 在Packet中增加getFamily方法,明確標識包所屬協議族,拋棄以往使用header標識包協議族的方式
7. 不再允許Port直接觸發QQ事件,降低了他們之間的耦合
重構無止境,但是目前這樣已經能滿足我增加網絡硬盤功能的需要,所以暫時就這樣。
我們來了解一下自2006 M2之后,核心層有了什么樣的變化
JQL里面有很多關鍵部件,先介紹一些概念
1. Porter,啟動一個異步I/O循環,直接監聽網絡事件
2. Port, 代表了一個端口,或者說代表了一個連接,Port需要注冊到Porter中,才能獲得網絡事件
3. Parser,代表了一個解析器,用來解析某個協議族的包
4. PacketHistory, 是包的緩沖,用來檢測重復包
5. Processor,代表了一個處理器,用來處理某個協議族的包,繼而觸發QQ事件
6. QQClient,代表了一個QQ客戶端
在2006 M2之前
1. 一個QQClient只有一個Porter
2. 一個Porter可以有多個Port
3. 在一個QQClient中,一個協議族只有一個Parser
4. 在一個QQClient中,一個協議族一個Processor
5. 一個QQClient只有一個PacketHistory
那么在2006 M2之后
1. 不變
2. 不變
3. 在一個Port中,一個協議族一個Parser,一個Port支持哪些協議族,通過PortPolicy指定
4. 不變
5. 一個Parser一個PacketHistory
所以關鍵的改變在3,5。通過3,使Port更加靈活。通過5,使包重復檢測的機制更加靈活。其實,4也應該變一下,但是目前沒需求,所以暫時不變。
曾經提過,這些改變是為了實現網絡硬盤功能的關系。網絡硬盤的協議和其他協議族有一些不同
1. 網絡硬盤協議包并不完美匹配之前的包層次假設
2. 網絡硬盤協議包可以非常長,帶來了解析上和處理上的新需求
再細的我就不說了,自己領會