Vincent.Chan‘s Blog

常用链接

统计

积分与排名

网站

最新评论

LumaQQ源代码指南

对于有些喜欢修改源代码的人,这里是一个指南的开端。我想到什么说什么,第一篇说说如何修改源代码让你用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. 网络硬盘协议包可以非常长,带来了解析上和处理上的新需求

再细的我就不说了,自己领会

posted on 2006-02-25 01:15 Vincent.Chen 阅读(1110) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: