2011年2月13日星期日

Miranda上GTalk的隐身问题

用Miranda上GTalk有挺长一段日子了,隐身的功能一直有问题。虽然GTalk上的朋友没有很烦人的那种,所以对这个功能需求并不强烈,不过每每想起还是有一点疙瘩在。前些天乱逛的时候看到一些相关资料,这几天零零碎碎又作了一些尝试,算是知道这是怎么一回事了吧…

Jabber/XMPP关于隐身的规范有三个,分别是:

按照规范文本的说法,XEP-0018已被拒绝,而XEP-0186又被推迟,目前只有XEP-0126是受XMPP委员会认可的规范。然而不知道出于什么原因,GTalk三个规范都没有采用,而是自己定义了一个google:shared-status的扩充规范来实现这个功能。

(evil不evil的,有人提到GTalk之所以不支持XEP-0126,是因为GTalk不支持XEP-0126的基础规范XEP-0016: Privacy Lists。至于GTalk为啥不支持XEP-0016,嗯,不知道。)

就结果来看,Jabber社区对Google的这个决定挺排斥的。各种流行的Jabber客户端,如Pidgin、Miranda、Psi等等都没有提供Google Shared Status的官方支持。可不管怎么说Google的影响力都是摆在那里的,所以有人给Pidgin写了Gtalk Shared Status的插件,Psi Plus也支持Google Shared Status。Miranda嘛,bug列表上有而已…

我倒是有试了试为Miranda添加Google Shared Status支持,结果嘛,能用,不过也就是这样而已了。因为我对Jabber/XMPP可以说是一无所知,而Miranda的Jabber插件要整个看一遍也会非常花时间,所以代码都是直接冲着隐身这个目标去的。这么dirty的dirty hack提交给官方肯定不会被采纳,就丢在这里好了。要是有人拿去用遇到什么问题可以留言,能改我都会改。

Update 2011-09-18:

官方似乎对加入GSS支持兴趣不大,而且嫌我的补丁写得太脏,可要我去通读一遍插件代码好好的再弄个补丁出来,我也没那个精气神和闲工夫,所以事情就僵在这里了。好在Miranda的更新虽然频繁,可多是小修小补,我的补丁至今仍然能用。以后我还会不定期更新的,但肯定没法做到跟官方同步。如果有更新强迫症的同学,可下载补丁自行编译,Miranda很好编,连VC6都可以过。

http://www.box.net/shared/gqt36iedxi4n7bkzk1q7

Update 2012-02-18:

其实早在v0.9.34的changelog里就有一句:“Jabber: GTalk improvements with status modes”,不过下载回来试了试发现还是有问题,就一直懒得管。今天又想起这一茬,于是把SVN里的代码翻出来看了看,原来基本上就是我提交的那个补丁嘛。只不过我的代码被修改了一部分,虽然显得不那么脏了,可也导致了补丁工作不正常。于是又写了个补丁提交上去,结果嘛,还是以前那样。

添加进正式版里的一部分代码工作不正常,并且有办法让代码至少先跑起来,却还是不愿意做,有点搞不懂这些人是怎么想的。

新的补丁很简单,打开jabber_util.cpp,将第894行由:

if (!m_bGoogleSharedStatus) p << XATTR( _T("type"), _T("invisible"));

改为:

if (!m_bGoogleTalk) p << XATTR( _T("type"), _T("invisible"));

之前编译的插件放上来一年多了,都没人下载过,这次我也懒得放了,万一有人需要的话,自己编一下吧。

2010年12月29日星期三

RGD - libgd binding for Ruby

前些日子在硬盘里乱逛,看到以前写的那一堆半成品,突然觉得自己还蛮不负责的。想说挑一些别人可能用得上的稍微整理一下吧,也算是给自己一个交待吧…

Ruby图片处理一直以来似乎都是用ImageMagick,可这东西即使没有内存泄漏的问题也显得过于庞大了,而且Win32下gem装上的包会把Ruby目录搞得非常恶心,所以一年多以前我根据只支持1.8的Ruby/GD改了一个出来用。

因为Ruby API在IO的部分变化大了点,再加上我对图片处理以及GD其实不熟,后来改着改着就完全重写了一遍。但因为代码并不是集中在一个时间段内完成的,所以还是会有些乱。好在就是这次整理没太偷懒,至少把libgd官方的文档都搬了过来,好歹算有点样子了吧。

代码托管在github:https://github.com/oTnTh/rgd

安装可以直接用gem:gem install rgd

用Windows的同学可以抓precompiled的版本,1.8和1.9同时支持:gem install rgd --platform x86-mingw32

文档的话,http://rubydoc.info/gems/rgd似乎还没更新?反正rdoc是可用的,暂时先看本地的吧。

有bug和问题欢迎找我。

以上。

2010年11月17日星期三

微软NLS文件格式

最近一直在纠缠Ruby的字符串编码问题,其中就涉及到了CP936、CP950和CP951等代码页的码表。想说与其去翻不知道靠谱与否的资料,不如直接从系统里的NLS文件中提取数据,这又牵涉到了NLS的文件格式问题。

网上能找到的NLS文件格式信息很少,Konstantin Kazarnovsky童鞋在2002年写的一篇是其中最详细的了。不过比对一下c_936.nls等双字节编码发现,那篇东西错处还是不少,表格也很不知所云。于是打开WinHex猜了老半天,算是有了一点成果吧。

注:NT和非NT系统的NLS文件格式有所不同,下面的内容只适用于NT系统内的NLS文件。

以下是文件头信息,还蛮简单的:

地址 字节长 备注
0x00 2 文件标志,NT内的NLS应为0x000D(注一)
0x02 2 代码页,如936
0x04 2 1表示单字节编码,2表示双字节编码
0x06 8 四个0x003F,疑似为转换表中的无效值(注二)
0x0E 12 前导字节(注三)

注一:Win9X内的NLS文件中,该字段为0表示SBCS编码,为1表示DBCS编码。

注二:根据ANSI/OEM代码页以及SBCS/DBCS的不同,NLS文件中最多会有四张转换表,此处的四个DWORD很可能表示四张表中无对应字符时的值,不过貌似所有NLS文件此处都是四个0x003F就是。另外Win32中CPINFOEX结构体内有一个UnicodeDefaultChar成员似乎也都返回0x003F,或许就是这么来的。

注三:针对DBCS编码,此处保存第一字节可能的取值,SBCS编码该部分全为0,DBCS如CP936就是0x81和0xFE。取这12个字节的最小值并左移8位,如CP936就是0x8100,下面提到的CP2UC表DBCS部分,就是从这个值开始保存的。呃,至少CP936和CP950是这样。

接下来是Codepage To Unicode(下称CP2UC)和Unicode To Codepage(下称UC2CP)转换表。根据ANSI/OEM代码页以及SBCS/DBCS的不同,转换表一共可能有二到四张。

地址 字节长 备注
0x1A 2 CP2UC转换表长度(@t_len),类型和单位都是DWORD(注一)
0x1C 512 CP2UC转换表,0x00到0xFF部分,每字符2字节
0x021C 6 / 2 疑似分隔符,ANSI SBCS代码页此处6字节长,否则2字节长(注二)
 0x021E 512 CP2UC转换表,OEM部分,每字符2字节,ANSI SBCS代码页无此部分
 0x041E 4 / 2 疑似分隔符,OEM SBCS代码页此处4字节长,否则2字节长
  0x0420 (@t_len - 515)*2 CP2UC转换表,双字节部分,每字符2字节,非DBCS代码表无此部分
  -(注三) 2 疑似分隔符,非DBCS代码表无此部分
0x0222 / 0x0422 / - 65536 / 131072 UC2CP转换表,SBCS代码页每字符1字节,否则每字符2字节

注一:该值其实就三种,ANSI SBCS为0x103,OEM SBCS为0x203,其他则为DBCS。

注二:间隔符共6字节。CP2UC可能有1到3张表,每两张表之间间隔2字节,末尾补齐6字节。间隔符并不总是0,是否有啥含义未知。

注三:嗯,随便注一下,0x420 + (@t_len - 515)*2,谁不会算呢?

上表貌似画得挺复杂,可我已经尽力了……

从文件格式来看,NLS最多只能处理二字节编码,而GB18030最长需要4个字节,难怪CP54936不可能成为系统代码页。而且NLS格式的Unicode部分只覆盖了BMP,但较新版本的Big5-HKSCS有相当一部分却是在SIP(0x2XXXX),恐怕这也是CP951只能支持到Big5-HKSCS:2001的原因。

再次感叹一下,Ruby 1.9居然还在跟ANSI纠缠,实在是馊到不能再馊的馊主意,真不知道那几枚大神究竟是怎么想的。

最后是代码,随便参考一下吧:

require 'stringio'

module OtNtH;module FileFormat
  class MS_NLS
    attr_reader :codepage, :bytes_pre_char, :leadbytes
    attr_reader :oem_to_uc, :cp_to_uc, :uc_to_cp
    
    def initialize(fpath)
      s = StringIO.new(File.open(fpath, 'rb') { |f|f.read }, 'rb')
      def s.read_i2
        self.readbyte + (self.readbyte << 8)
      end

      # 文件头标志,0x0d表示NT格式的NLS
      buf = s.read_i2
      if buf == 0 then
        raise 'Win9x single-byte (SBCS) NLS Format!'
      elsif buf == 1 then
        raise 'Win9x double-byte (DBCS) NLS Format!'
      elsif buf != 0x0d
        raise 'Unknow File Format!'
      end
      
      @codepage = s.read_i2
      @bytes_pre_char = s.read_i2 # 为1则是SBCS单字节编码,为2则是DBCS
      
      s.pos += 8 # 0x003F * 4,可能是CPINFOEX里边的UnicodeDefaultChar
      
      buf = s.read(12) # 前导字节,如cp936是81和fe
      @leadbytes = []
      buf.strip.each_byte { |b| @leadbytes.push(b.to_s(16)) }
      
      @t_len = s.read_i2 # 转换表长度,cp936为32771
      # 0x0103表示单字节ANSI代码页,0x0203表示单字节OEM代码页
      # 为其他值时应该都是DBCS代码页
      raise 'Broken NLS File?' if s.size != s.pos + @t_len*2 + 0x10000*@bytes_pre_char
      
      @oem_to_uc = {}
      @cp_to_uc = {}
      @uc_to_cp = {}
      
      # 所有代码页都有的一张表
      0x100.times { |n| @cp_to_uc[sprintf('0x%.4X', n)] = sprintf('0x%.4X', s.read_i2) }
      
      while true do
        if @t_len == 0x103 then
          s.pos += 6
          break
        end
        
        s.pos += 2 # 未知内容,437为1,936和950都是0
        0x100.times { |n| @oem_to_uc[sprintf('0x%.4X', n)] = sprintf('0x%.4X', s.read_i2) }
        if @t_len == 0x203 then
          s.pos += 4
          break
        end
        
        s.pos += 2 # 未知内容,cp936是0
        b = (@leadbytes.min + '00').to_i(16)
        # 256 + 1 + 256 + 1 + .... + 1
        (@t_len-515).times { |n| @cp_to_uc[sprintf('0x%.4X', b+n)] = sprintf('0x%.4X', s.read_i2) }
        
        s.pos += 2 # 未知内容,936和950为4
        break
      end
      read_unit = @bytes_pre_char == 1 ? :readbyte : :read_i2
      0x10000.times { |n| @uc_to_cp[sprintf('0x%.4X', n)] = sprintf('0x%.4X', s.send(read_unit))}
      
    end
  end
end;end

nls = OtNtH::FileFormat::MS_NLS.new(ARGV[0])
ms = {}
nls.cp_to_uc.each do |cp, uc|
  n = uc.hex
  next if uc == '0x003F' || cp.hex == n || n == 0
  ms[cp] = uc.to_i(16)
end

n = 0
ms.each do |cp, uc|
  n += 1 if cp.hex != nls.uc_to_cp['0x%.4X' % uc].hex
end
puts n