浏览模式: 标准 | 列表
11月
27

系统汉化

 要对系统进行汉化与其说是个技术活,不如说是个力气活,但确实又少不了细心。系统汉化可以分为应用汉化、框架汉化、Luna汉化和其它部分汉化。

应用汉化

ROM中预先安装好的应用都在rootfs/usr/palm/applications中,对每个应用汉化时要注意,不同的程序编写方式,汉化方式也不一样。mojo和enyo的程序在多国语言化方面有些差异。

MOJO程序汉化

mojo程序汉化时,在相应的包名目录下,建一个resources/zh/cn目录。

如果要汉化桌面图标上的显示文字,全局搜索等内容,你需要建立一个appinfo.json的文件,这个文件内容可以直接把从软件包中的这个文件复制过去然后修改,修改时需要注意,图标位置,执行文件的位置等内容,一定要加上../../../这样的路径,否则这些文件找不到。

如果你要汉化js中的那些为实现多国语言已经做了处理的字符串(就是用$L()包含的那些字符串),那你需要再建立一个strings.json文件,其中的内容格式为:

  1. {  
  2.     "OK""确定",  
  3.     "Cancel""取消"  
  4. }  

如果字符串中包含一些特殊字符,一定要注意要用json的转义方式来写。

还有些需要汉化的字符串是在views中的html中的,这些内容的汉化,只需要在这个目录下建立对应的目录,将原始的html复制到这些目录下,对其进行汉化即可。

有时候汉化还涉及到css的一些调整,同样把css文件复制过来做修改就可以啦。

ENYO程序汉化

enyo的程序汉化相对来说简单些。

对appinfo.json的汉化与mojo相同。

对字符串的汉化只需要在resources下建一个zh_cn.json的文件就可以了,内容格式跟mojo的strings.json格式相同。

enyo没有views这种东西,所以就不用管了。

框架汉化

在rootfs/usr/palm/frameworks下的许多框架文件也需要汉化。具体那些文件需要汉化就不说了,可以参考其它多语言化文件来处理。

frameworks这里面有些目录在一些比较新版本的原始ROM中就已经包含了汉化内容,比如mojo,mojo2,enyo这些目录,所以如果发现他们里面原本就有汉化内容的,就不需要重复汉化了。

LUNA汉化

在rootfs/usr/lib/luna下,有部分资源是需要汉化的,其中包括luna-network和system。汉化的内容跟汉化的方法跟mojo应用程序相同。

其中关于信标的汉化最主要的部分也在这里。在rootfs/usr/lib/luna/system/luna-systemui/resources/zh/cn/strings.json中,添加:

  1. "CHINA  MOBILE""中国移动",  
  2. "CHN-UNICOM""中国联通",  
  3. "China Telecom""中国电信",  
  4. "China Unicom""中国联通",  
  5. "CMCC""中国移动",  

就可以实现对信标的汉化了。注意上面"CHINA  MOBILE"中间是两个空格,而不是一个。

但要实现更完美的信标汉化,还需要在:

rootfs/usr/palm/applications/com.palm.app.phoneprefs/resources/zh/cn/strings.json

rootfs/usr/palm/sysmgr/localization/zh_cn/strings.json

里面都添加上这一段内容。

对于一卡多号的用户来说,上面这些数据可以能对您的信标中文化无效,因为一卡多号的卡里面的信标内容跟上面的是不同的,有同学反映他的一卡多号的信标后面还带有手机号,所以对于这种卡就没办法做统一的汉化了,但是使用者可以按照这种方法,把自己那些特殊的信标加到这里面。

其它部分汉化

在rootfs/usr/palm/public和rootfs/usr/palm/sysmgr里面也有一点资源需要汉化。至于那些文件,可以参考这里面的其它语言的资源。

这里主要提一点关于启动器中那些页面标题的汉化,这些标题的汉化可以在rootfs/usr/palm/sysmgr/localization/zh_cn/strings.json中添加上。

而且不但可以添加默认的那几个(Applications、Extras、System这些),你还可以添加用户可能会建立的新页面的标题,比如Games、News等,这些添加之后,用户在建立具有这些名称的页面之后,当再次重启系统时候,这些英文名在中文界面下也会自动变成中文了。

最后还要强调一下,汉化是个细心的活,要想完美汉化,对每个词每个句子甚至每个缩写都要字斟句酌才能得到完美的结果。

举个例子,比如在日历的汉化中,有几个英文序数词结尾的汉化,这些序数词后缀分别是"nd"、"rd"、"st"、"th",如果你不了解上下文,当看这几个词你可能不知道什么意思(但如果你够聪明的话,当看到这几个摆在一起时应该也会想到)。我们知道在中文里序数词和基数词是没区别的(如果非要说有区别的话,那区别就是中文的序数词是在基数词前面加一个“第”字),那么在汉化这几个词时,我们就应该把它们汉化为空字符串,而不应该原样保留(因为我们没办法把后缀汉化改成前缀汉化,所以不能把它们汉化成“第”)。

类似的地方还有好多,这里仅仅是举个例子。从这里我们可以看出,汉化的确不是一件容易的事情,所以,在这里我要向一直以来提供系统汉化包的开发者们表示崇高的敬意!

汉化包的打包

汉化包在打包时,如果考虑到集成到ROM中,并且可以方便卸载的话,最好的方式是把这些新添加的汉化资源单独拿出来打成一个包,这些资源在包里面就以原始路径存在,不需要postinst、postrm之类的脚本,直接打包之后,放在<carrier>.tar中就可以啦。

这样做的好处是,便于汉化包的卸载和升级。

但是这种包直接提供给用户安装的话,用户还需要一定的Linux命令行知识,能够自己使用ipkg命令来安装卸载包才行。因为这些包是不能通过preware、WQI来安装的,否则安装位置是不对的。

现在直接提供给用户安装的汉化包,大部分都是以应用程序方式来打包,然后通过postinst方式来将汉化资源再复制到相应的汉化目录下。这种汉化包对用户来说,便于使用preware、WQI来安装,但是却不方便卸载,也不方便集成到ROM中。

所以,两种方式各有利弊,根据具体情况来做选择就行了。

11月
26

将ROM分解为IPK

 前面6节基本上已经把ROM定制的具体过程都讲完了。从这节开始,我们来讨论一点高级内容。不过本人水平有限,以下内容算是抛砖引玉了。

前面在ROM文件结构一节中,我们已经提到,rootfs下的大部分文件都是预安装好的包,那我们有没有可能将这些包都还原为ipk呢?

当然可以。

下面我们就来看一下这个过程该如何实现。

首先我们需要把ROM分解,分解的工具最方便的还是使用MetaDoctor里面的unpack-doctor这个脚本。在Linux下,我们最好是用root帐号来执行这个命令,以保证包中文件的权限不会改变。

展开之后,我们可以在rootfs/usr/lib/ipkg/info中找到所有已安装的包的control文件,因为每个包都有一个对应的control,所以我们以这个control文件为依据,就可以列出系统内所有的包。

之后循环读取这些control文件名,然后通过它来获取包名,然后用包名为该包建立一个目录,在其中再创建CONTROL和data这两个目录,然后把control复制(或移动)到CONTROL目录下,然后把该包对应的preinst、postinst、prerm、postrm脚本也都统统复制(或移动)到CONTROL目录下,最后再根据list文件中的内容,将该包中包含的文件及其目录复制(或移动)到data目录下,最后使用ipkg-build脚本,对该目录进行打包就可以啦。

下面是将ROM分解为ipk文件的完整脚本:

  1. #!/bin/sh  
  2.   
  3. [ -f $1 ] || exit 1  
  4.   
  5. currpath=`pwd`  
  6. $currpath/unpack-doctor $1  
  7.   
  8. NAME=`basename $1 .jar`  
  9.   
  10. rm -rf $NAME/ipkgs $NAME/build  
  11. mkdir -p $NAME/ipkgs $NAME/build  
  12.   
  13. cd $NAME/ipkgs  
  14.   
  15. ROOTFS=$currpath/$NAME/rootfs  
  16. INFOPATH=$ROOTFS/usr/lib/ipkg/info  
  17.   
  18. ls $INFOPATH/*.control | while read control; do  
  19.     package=`basename $control .control`  
  20.     PACKAGE=$currpath/$NAME/build/$package  
  21.     CONTROL=$PACKAGE/CONTROL  
  22.     data=$PACKAGE/data  
  23.     mkdir -p $CONTROL $data  
  24.     cp $control $CONTROL/control  
  25.     if [ -f $INFOPATH/$package.preinst ]; then  
  26.         mv $INFOPATH/$package.preinst $CONTROL/preinst  
  27.     fi  
  28.     if [ -f $INFOPATH/$package.postinst ]; then  
  29.         mv $INFOPATH/$package.postinst $CONTROL/postinst  
  30.     fi  
  31.     if [ -f $INFOPATH/$package.prerm ]; then  
  32.         mv $INFOPATH/$package.prerm $CONTROL/prerm  
  33.     fi  
  34.     if [ -f $INFOPATH/$package.postrm ]; then  
  35.         mv $INFOPATH/$package.postrm $CONTROL/postrm  
  36.     fi  
  37.     if [ -f $INFOPATH/$package.list ]; then  
  38.         cat $INFOPATH/$package.list | while read filename; do  
  39.             pathname=`dirname "$filename"`  
  40.             mkdir -p "$data/$pathname"  
  41.             mv -f -T "$ROOTFS/$filename" "$data/$filename"  
  42.         done  
  43.         $currpath/ipkg-build $PACKAGE  
  44.     fi  
  45. done  
  46.   
  47. cd $currpath  

把这个保存为一个脚本,然后在同一个目录下放上unpack-doctor和ipkg-build脚本,然后再把要展开的ROM放在这个目录下,这个脚本的目录下运行脚本后面跟上ROM名称,就可以将ROM分解为ipk了。

理解了以上脚本之后,根据该原理,就也可以很方便的完成ROM杂交,ROM内置安装包移除等脚本了。这些脚本的编写就看各位高手的啦。

11月
26

集成安装IPK

 完成了ipk的打包之后,我们需要将修改过的或者新添加的ipk包加入到我们自己要定制的ROM当中。但ipk包的安装位置是有区别的,一种是安装到root的ipk包,另一种是安装到/media/cryptofs/apps下的ipk包。

安装到root的ipk包,有两种集成方式,一种是预安装方式,一种是放在<carrier>.tar中(<carrier>表示att、wr、verizon等)。

预安装方式采用跟原始ROM中那些预安装的包一样的方法,MetaDoctor里面有两个选项EXTRA_ROOTFS_IPKGS和EXTRA_ROOTFS_TARBALL,把事先修改过的包手动展开按照ROM文件结构打包成tar文件,然后放在跟Makefile相同的目录下,然后设定好这个EXTRA_ROOTFS_TARBALL这个参数,然后把跟这个tar里面有关的包名放在EXTRA_ROOTFS_IPKGS这个参数里,就可以了。这种方式比较麻烦,而且容易出错,修改之后对root分区镜像的影响是永久性的,所以,这不是推荐的方式。

而放在<carrier>.tar中则非常方便。但是仍然有需要注意的问题,如果ROM中已经有预安装的同名的包,需要先手动从rootfs镜像中删除。删除方法不难,但比较繁琐:

到rootfs/usr/lib/ipkg/info下找到跟要删除的包名相关的所有文件(control,list,preinst等),根据这里面的list文件中的内容,找到这里面的这些文件一一删除,修改rootfs/md5sums.tar里面的md5sums文件,将其中跟这个包有关的所有内容删除。然后根据control文件中所描述的构架到rootfs/usr/lib/ipkg/lists中找到对应构架的那个文件,打开它将其中关于这个包的部分删除掉。打开rootfs/usr/lib/ipkg/status,将其中跟这个包有关的内容删除掉。把rootfs/usr/lib/ipkg/info中的跟这个包有关的所有文件删除。最后再次修改rootfs/md5sums.tar里面的md5sums文件,将修改过的rootfs/usr/lib/ipkg/lists/oe-<arch>和rootfs/usr/lib/ipkg/status这两个的md5sum值重新计算后更新。

以上步骤可以通过写一个脚本来自动完成,有兴趣的同学可以自己来写。

另一种是安装到/media/cryptofs/apps中的包,这些包实际上并不是在刷机过程中安装的,而是在刷机之后配置完毕的第一次启动时安装的。

在webOS 2.1中,系统给出了一个这样的例子。那就是系统里内置的app-ipkgs这个包。这个包在webOS 2.2.x和webOS 3.x中有更新,更新之后的安装方式有些变化,我们暂时不管,我们先来看看webOS 2.1中的app-ipkgs这个包是怎么做的。

这个包包含2个部分,一部分是/usr/palm/ipkgs这个目录下的一些安装包、图标和一个manifest.json文件,另一部分是/etc/event.d/app-install这个安装脚本。

/usr/palm/ipkgs下的安装包没什么好说的,单说manifest.json这个文件,这里面是安装包的一些包信息,如果你要安装的包的信息不在这个文件里,也没有什么太大影响。如果在这个文件里,在软件管理器中就看不到这个软件的删除选项。但在启动器中仍然可以通过按住白方块点图标删除。

我们重点看一下/etc/event.d/app-install这个脚本。

2.1的这个脚本很简单,就是将/usr/palm/ipkgs下的所有ipk列出来然后通过循环方式执行安装。

在安装时首先将安装文件复制到/media/internal/downloads下,其实复制到/media/internal的其它任何自建的目录下都可以,这样做是因为安装时,安装位置是需要可写入的,因为在安装过程中安装器会自动创建一个tmp目录。另外,如果要保证安装目录可写,也可以用rootfs_open –w的方法。这样可以省去文件的复制过程。

然后通过:

ipkg –o /media/cryptofs/apps install $package

命令安装。

最后通过:

luna-send -n 1 palm://com.palm.applicationManager/forceSingleAppScan '{"id":"'$pkg'"}'

来使安装的软件生效(桌面上就可以看到图标了)。

这样的一个安装过程,对于安装没有安装依赖顺序,没有postinst脚本的ipk来说是足够了。

但对于带有postinst脚本、prerm等脚本的应用来说还是有些问题的。因为ipkg –o 方式安装时,不会自动执行postinst脚本,这就使得带有postinst脚本的安装程序不能被完全安装。

我们可以在ipkg –o 之后加上这样一段:

  1. #check if install succeeded, then run postinst if needed  
  2. if [ -f $APPS/usr/lib/ipkg/info/$pkg.control ] ; then  
  3.     if [ -f $APPS/usr/lib/ipkg/info/$pkg.prerm ] ; then  
  4.         if [ ! -f $APPS/.scripts/$pkg/pmPreRemove.script ] ; then  
  5.             /bin/mkdir -m 777 -p $APPS/.scripts/$pkg  
  6.             /bin/cp -f $APPS/usr/lib/ipkg/info/$pkg.prerm $APPS/.scripts/$pkg/pmPreRemove.script  
  7.         fi  
  8.     fi  
  9.     if [ -f $APPS/usr/lib/ipkg/info/$pkg.postinst ] ; then  
  10.         echo "$UPSTART_JOB: Running $pkg.postinst" | logger  
  11.         export IPKG_OFFLINE_ROOT=$APPS ; /bin/sh $APPS/usr/lib/ipkg/info/$pkg.postinst  
  12.     fi  
  13. fi  

就实现了这些安装脚本的处理。

但是对于有安装依赖顺序的ipk来说,通过for循环目录可能无法保证正确的安装顺序,所以我们可以人为的来写一个安装文件列表,比如叫packages(其实叫什么都行),里面一行一个ipk文件名,例如:

  1. com.palm.app.findapps_2.0.23300_all.ipk  
  2. org.webosinternals.preware_1.8.5_arm.ipk  
  3. org.webosinternals.diffstat_1.45-1_armv7.ipk  
  4. org.webosinternals.lsdiff_0.3.1-1_armv7.ipk  
  5. org.webosinternals.patch_2.5.9-4_armv7.ipk  
  6. org.webosinternals.unzip_6.0-1_armv7.ipk  
  7. org.webosinternals.zip_3.0-1_armv7.ipk  
  8. ca.canucksoftware.js-service-framework_1.0.1_all.ipk  
  9. ca.canucksoftware.filemgr_2.0.7_all.ipk  
  10. ca.canucksoftware.internalz_1.5.0_all.ipk  
  11. org.webosinternals.patches.misc-unthrottle-download-manager_2.1.0-160_all.ipk  

这样一个格式,只要这个文件里的包安装顺序正确,就可以用for循环来读取这个文件来保证顺序了。

然后还有一点小问题,就是如果安装的程序里包含有服务,例如上面例子中的filemgr,这样安装之后,可能服务无法自动启动,原因是在/var/palm/ls2/roles/prv和pub下没有生成服务规则文件。本来这些文件应该是在程序安装之后,系统根据usr/palm/services/包名/services.json自动生成的,但既然系统没有生成,那我们就自己创建一下这两个文件,加入下面的代码可以完成规则文件的创建:

  1.         if [ -f $APPS/usr/palm/services/$pkg/services.json ] ; then  
  2.             echo "{  
  3.     \"role\": {  
  4.         \"exeName\":\"js\",  
  5.         \"type\": \"regular\",  
  6.         \"allowedNames\": [\"$pkg\"]  
  7.     },  
  8.     \"permissions\": [  
  9.         {  
  10.          \"service\":\"$pkg\",  
  11.          \"inbound\":[\"*\"],  
  12.          \"outbound\":[\"*\"]  
  13.         }  
  14.     ]  
  15. }" > /var/palm/ls2/roles/prv/$pkg.json  
  16.             chmod +x /var/palm/ls2/roles/prv/$pkg.json  
  17.             cp /var/palm/ls2/roles/prv/$pkg.json /var/palm/ls2/roles/pub/$pkg.json  
  18.         fi  

最后,我们修改之后的完整脚本是这样子的:

  1. # -*- mode: shell-script; -*-  
  2.   
  3. start on stopped configurator  
  4.   
  5. script  
  6.     rootfs_open -w  
  7.   
  8.     APPS=/media/cryptofs/apps  
  9.     IPK_DIR=/usr/palm/ipkgs  
  10.   
  11.     for package in $(cat $IPK_DIR/packages)  
  12.     do  
  13.         echo "$UPSTART_JOB: attempt to install $package" | logger  
  14.   
  15.         #call luna-send to begin installation  
  16.         ipkg -o $APPS install $IPK_DIR/$package | logger  
  17.   
  18.         # Get the pkg name from the file name.  
  19.         local bname=${package##*/}  
  20.         local rname=${bname%.ipk}  
  21.         local pkg=${rname%%_*}  
  22.   
  23.         if [ -f $APPS/usr/palm/services/$pkg/services.json ] ; then  
  24.             echo "{  
  25.     \"role\": {  
  26.         \"exeName\":\"js\",  
  27.         \"type\": \"regular\",  
  28.         \"allowedNames\": [\"$pkg\"]  
  29.     },  
  30.     \"permissions\": [  
  31.         {  
  32.          \"service\":\"$pkg\",  
  33.          \"inbound\":[\"*\"],  
  34.          \"outbound\":[\"*\"]  
  35.         }  
  36.     ]  
  37. }" > /var/palm/ls2/roles/prv/$pkg.json  
  38.             chmod +x /var/palm/ls2/roles/prv/$pkg.json  
  39.             cp /var/palm/ls2/roles/prv/$pkg.json /var/palm/ls2/roles/pub/$pkg.json  
  40.         fi  
  41.   
  42.         #check if install succeeded, then run postinst if needed  
  43.         if [ -f $APPS/usr/lib/ipkg/info/$pkg.control ] ; then  
  44.             if [ -f $APPS/usr/lib/ipkg/info/$pkg.prerm ] ; then  
  45.                 if [ ! -f $APPS/.scripts/$pkg/pmPreRemove.script ] ; then  
  46.                     /bin/mkdir -m 777 -p $APPS/.scripts/$pkg  
  47.                     /bin/cp -f $APPS/usr/lib/ipkg/info/$pkg.prerm $APPS/.scripts/$pkg/pmPreRemove.script  
  48.                 fi  
  49.             fi  
  50.             if [ -f $APPS/usr/lib/ipkg/info/$pkg.postinst ] ; then  
  51.                 echo "$UPSTART_JOB: Running $pkg.postinst" | logger  
  52.                 export IPKG_OFFLINE_ROOT=$APPS ; /bin/sh $APPS/usr/lib/ipkg/info/$pkg.postinst  
  53.             fi  
  54.         fi  
  55.   
  56.         echo "$UPSTART_JOB: attempt to scan $pkg" | logger  
  57.   
  58.         # Have the applicationManager rescan the pkg so that it will   
  59.         # show up in the launcher.  
  60.         returnVal=$(luna-send -n 1 palm://com.palm.applicationManager/forceSingleAppScan \  
  61.             '{"id":"'$pkg'"}' 2>&1)  
  62.   
  63.         logger "$UPSTART_JOB: returnVal is $returnVal"  
  64.     done  
  65. end script  

现在我们可以把这个包单独拿出来,然后把rootfs下的这个预先安装的这个包删掉。最后修改之后,放在<carrier>.tar里面就可以啦。

当然,更好的方法是,不要动系统的这个app-ipkgs。自己创建一个类似的包,脚本按照自己设置的目录来写。最后单独打一个包,名字不要跟系统已有的包冲突,最后放在<carrier>.tar中就可以了。自己创建类似安装包的好处是,它可以减少你对rootfs的修改,保证系统自带的升级程序不会被破坏,另外还能适用于1.x、2.2.x和3.x系统,而不仅仅局限于2.1.x系统。

11月
25

修改打包IPK

上一节我们讲到WebOS Doctor中的rootfs实际上是一组预先安装好的包(ipk)的集合,那我们如何自己修改和打包ipk呢?这一节我们就来讲讲这个。

ipk文件实际上是用ar命令打包的一个归档包。没有数字签名的ipk中一般包含control.tar.gz、data.tar.gz和debian-binary这三个文件。

其中debian-binary里面是固定的。

control.tar.gz中包含了control、md5sums、preinst、postinst、prerm、postrm这几个文件,其中control是必须的,其它都是可选的。

data.tar.gz中包含了要安装的程序和数据。

ipk的安装对于大多数用户来说都是使用preware、WebOS Quick Install、Internalz Pro来安装程序的。如果不考虑postinst脚本,那么使用这些工具安装的文件都是相对于/media/cryptofs/apps目录进行安装的。如果直接使用不带-o参数的ipkg install命令安装的话,那么安装的文件都是相对于/目录的。也就是说ipk包中实际上并不包含相对于那个路径进行安装的信息。安装到哪儿只跟安装的方式有关。

rootfs.tar.gz中那些预先安装好的包都是相对于/目录安装的。相对于/目录安装的程序,用户通过preware、WebOS Quick Install和系统自带的软件包管理器都是无法卸载的,但并不是说就真的无法卸载,实际上只要你愿意,这些包都可以在root帐号下用ipkg remove命令来卸载掉。

我们在上一节中有一个<carrier>.tar没有介绍(<carrier>表示att、wr、verizon等),之所以没介绍主要原因就是我们需要先了解上面的这些内容,然后才比较容易解释这个<carrier>.tar。

这个<carrier>.tar中的主要内容就是一些ipk包,另外,还有一个installer.xml。这个installer.xml如果不是要做跨运营商或跨机型移植,是不需要修改的。那么剩下的就是运营商定制的一些ipk包了。这些包是在刷机程序把rootfs.tar.gz写入设备之后,进行安装的。他们都是相对于/目录进行安装的。因此这些包从本质上来讲,跟rootfs.tar.gz中预先安装的包是没有区别的。唯一的区别就是rootfs.tar.gz中的包是预先安装好的,<carrier>.tar中的包是在刷机过程中进行安装的。而ROM验证md5sums的过程是在<carrier>.tar中的所有ipk安装之后才进行的。因此,<carrier>.tar中的包也是需要进行md5sums验证的。

既然<carrier>.tar中的包也需要验证,所以对于放在<carrier>.tar中的包来说,它的control.tar.gz中的md5sums不是可选的,而是必须的。如果缺少了这个md5sums,那么刷机到82%时,同样会因为无法通过md5sums验证而终止刷机,无法重启。

要生成一个带md5sums的ipk,如果靠手工来计算编写md5sums,并自己通过tar、gzip、ar等命令来打包实在是麻烦的很。实际上前人早在10年前就做好了这样的打包脚本,叫ipkg-build。我们可以直接拿来用。

下面是这个脚本的完整内容:

  1. #!/bin/sh  
  2.   
  3. # ipkg-build -- construct a .ipk from a directory  
  4. # Carl Worth <cworth@east.isi.edu>  
  5. # based on a script by Steve Redler IV, steve@sr-tech.com 5-21-2001  
  6. set -e  
  7.   
  8. ipkg_extract_value() {  
  9.     sed -e "s/^[^:]*:[[:space:]]*//"  
  10. }  
  11.   
  12. required_field() {  
  13.     field=$1  
  14.   
  15.     value=`grep "^$field:" < $CONTROL/control | ipkg_extract_value`  
  16.     if [ -z "$value" ]; then  
  17.         echo "*** Error: $CONTROL/control is missing field $field" >&2  
  18.         return 1  
  19.     fi  
  20.     echo $value  
  21.     return 0  
  22. }  
  23.   
  24. pkg_appears_sane() {  
  25.     local pkg_dir=$1  
  26.   
  27.     local owd=`pwd`  
  28.     cd $pkg_dir  
  29.   
  30.     PKG_ERROR=0  
  31.   
  32.     large_uid_files=`find . -uid +99`  
  33.     if [ -n "$large_uid_files" ]; then  
  34.         echo "*** Warning: The following files have a UID greater than 99.  
  35. You probably want to chown these to a system user: " >&2  
  36.         ls -ld $large_uid_files  
  37.         echo >&2  
  38.     fi  
  39.           
  40.   
  41.     if [ ! -f "$CONTROL/control" ]; then  
  42.         echo "*** Error: Control file $pkg_dir/$CONTROL/control not found." >&2  
  43.         cd $owd  
  44.         return 1  
  45.     fi  
  46.   
  47.     pkg=`required_field Package`  
  48.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  49.   
  50.     version=`required_field Version | sed 's/.*://;'`  
  51.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  52.   
  53.     arch=`required_field Architecture`  
  54.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  55.   
  56.     required_field Maintainer >/dev/null  
  57.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  58.   
  59.     required_field Description >/dev/null  
  60.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  61.   
  62.     section=`required_field Section`  
  63.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  64.     if [ -z "$section" ]; then  
  65.         echo "The Section field should have one of the following values:" >&2  
  66.         echo "Games, Multimedia, Communications, Settings, Utilies, Applications, Console, Misc" >&2  
  67.     fi  
  68.   
  69.     priority=`required_field Priority`  
  70.     [ "$?" -ne 0 ] && PKG_ERROR=1  
  71.     if [ -z "$priority" ]; then  
  72.         echo "The Priority field should have one of the following values:" >&2  
  73.         echo "required, important, standard, optional, extra." >&2  
  74.         echo "If you don't know which priority value you should be using, then use \`optional'" >&2  
  75.     fi  
  76.   
  77.     if echo $pkg | grep '[^a-z0-9.+-]'; then  
  78.         echo "*** Error: Package name $name contains illegal characters, (other than [a-z0-9.+-])" >&2  
  79.         PKG_ERROR=1;  
  80.     fi  
  81.   
  82.     local bad_fields=`sed -ne 's/^\([^[:space:]][^:[:space:]]\+[[:space:]]\+\)[^:].*/\1/p' < $CONTROL/control | sed -e 's/\\n//'`  
  83.     if [ -n "$bad_fields" ]; then  
  84.         bad_fields=`echo $bad_fields`  
  85.         echo "*** Error: The following fields in $CONTROL/control are missing a ':'" >&2  
  86.         echo "  $bad_fields" >&2  
  87.         echo "ipkg-build: This may be due to a missing initial space for a multi-line field value" >&2  
  88.         PKG_ERROR=1  
  89.     fi  
  90.   
  91.     for script in $CONTROL/preinst $CONTROL/postinst $CONTROL/prerm $CONTROL/postrm; do  
  92.         if [ -f $script -a ! -x $script ]; then  
  93.             echo "*** Error: package script $script is not executable" >&2  
  94.             PKG_ERROR=1  
  95.         fi  
  96.     done  
  97.   
  98.     if [ -f $CONTROL/conffiles ]; then  
  99.         for cf in `cat $CONTROL/conffiles`; do  
  100.             if [ ! -f ./$cf ]; then  
  101.                 echo "*** Error: $CONTROL/conffiles mentions conffile $cf which does not exist" >&2  
  102.                 PKG_ERROR=1  
  103.             fi  
  104.         done  
  105.     fi  
  106.   
  107.     cd $owd  
  108.     return $PKG_ERROR  
  109. }  
  110.   
  111. ###  
  112. # ipkg-build "main"  
  113. ###  
  114.   
  115. case $# in  
  116. 1)  
  117.     dest_dir=.  
  118.     ;;  
  119. 2)  
  120.     dest_dir=$2  
  121.     ;;  
  122. *)  
  123.     echo "Usage: ipkg-build <pkg_directory> [<destination_directory>]" >&2  
  124.     exit 1   
  125.     ;;  
  126. esac  
  127.   
  128. pkg_dir=$1  
  129.   
  130. if [ ! -d $pkg_dir ]; then  
  131.     echo "*** Error: Directory $pkg_dir does not exist" >&2  
  132.     exit 1  
  133. fi  
  134.   
  135. # CONTROL is second so that it takes precedence  
  136. CONTROL=  
  137. [ -d $pkg_dir/DEBIAN ] && CONTROL=DEBIAN  
  138. [ -d $pkg_dir/CONTROL ] && CONTROL=CONTROL  
  139. if [ -z "$CONTROL" ]; then  
  140.     echo "*** Error: Directory $pkg_dir has no CONTROL subdirectory." >&2  
  141.     exit 1  
  142. fi  
  143.   
  144. if ! pkg_appears_sane $pkg_dir; then  
  145.     echo >&2  
  146.     echo "ipkg-build: Please fix the above errors and try again." >&2  
  147.     exit 1  
  148. fi  
  149.   
  150. tmp_dir=$dest_dir/IPKG_BUILD.$$  
  151. mkdir $tmp_dir  
  152.   
  153. (cd $pkg_dir/data; find . -type f -print0 | xargs -0 md5sum ) > $pkg_dir/$CONTROL/md5sums  
  154. if [ ! -f "$pkg_dir/files.txt" ]; then  
  155.   (cd $pkg_dir/data; find . -type f -print0 |xargs -0 grep '' -l) >$pkg_dir/files.txt  
  156. fi  
  157. # tar -C $pkg_dir/data -cf $tmp_dir/data.tar -T $pkg_dir/files.txt -h --verify  
  158. tar -C $pkg_dir/data -cf $tmp_dir/data.tar . --verify  
  159. gzip -f $tmp_dir/data.tar  
  160. tar -C $pkg_dir/$CONTROL -czf $tmp_dir/control.tar.gz .  
  161.   
  162. echo "2.0" > $tmp_dir/debian-binary  
  163.   
  164. pkg_file=$dest_dir/${pkg}_${version}_${arch}.ipk  
  165. #tar -C $tmp_dir -czf $pkg_file ./debian-binary ./data.tar.gz ./control.tar.gz  
  166. (cd $tmp_dir ;ar -qc $pkg_file ./debian-binary ./data.tar.gz ./control.tar.gz ; mv $pkg_file ../)  
  167. rm $tmp_dir/debian-binary $tmp_dir/data.tar.gz $tmp_dir/control.tar.gz  
  168. rmdir $tmp_dir  
  169.   
  170. echo "Packaged contents of $pkg_dir into $pkg_file"  

这个脚本我对它作过一处非常细微的修改,旧的脚本中在打包data.tar.gz时,对软连接和硬连接是将连接指向的文件进行打包的。而实际上我们没有必要这样做,我们可以直接将连接以原始方式打包到ipk的data.tar.gz中,这是ipk包允许的。而且实际上webOS系统中原本有好多包本来就是带有软连接的,如果不做这个修改,后面我们就不能正确的将系统全部打包回ipk。

好了,有了这个脚本,我们就可以自制ipk包了。首先建立一个包的目录,通常我们以包名来命名,然后在其下建立两个目录,分别为CONTROL和data,注意大小写。

CONTROL目录下放control.tar.gz解压之后的内容。其中不必包含md5sums,即使包含在打包时也会重新生成,因此不必担心md5sums的正确性。

data目录下放data.tar.gz解压之后的内容。注意它是相对于/的,里面的内容要包含相对于/的路径。另外,即使你希望这些内容被安装到/media/cryptofs/apps,也不要把这个路径建立到data目录下,这个路径是在安装时才决定的。

之后你可以对这两个目录下的内容进行修改编辑,添加删除内容都可以。修改完毕之后,退回到这个包目录的上级目录下,然后将其拥有者和用户组都改为root(对于普通包是这样的,对于系统包来说,我们应该在解压系统包时就保留系统包的用户组,在修改时不要变更原来文件的用户组)。最后执行ipkg-build 包名,就可以重新打包生成修改之后的ipk了。

上文中提到的ipkg-build可在此直接下载:ipkg-build

11月
25

ROM文件结构

前面我们对如何修改WebOS ROM有了一个大致的了解,但是要想更好的定制ROM,熟悉ROM的文件结构还是有必要的。

webOS的ROM被叫做webOS Doctor,虽然不同版本的webOS Doctor内容不同,但大致结构上是一样的。

webOS Doctor是一个jar文件,它是一个可执行的java压缩包。你可以把它改名为zip就可以直接用解压缩工具将它解压缩(当然不改名也可以解压缩)。

解压缩之后,我们得到了3个目录。它们分别是META-INF、com和resources。

其中META-INF从名字上看我们就知道他是这个jar包的元信息。原版的webOS Doctor的这个目录下有三个文件:JARKEY.RSA、JARKEY.SF和MANIFEST.MF。这三个文件包含了这个jar文件的数字签名信息和启动信息。数字签名是用来验证这个jar包是否被修改过的。如果我们要修改webOS Doctor,就需要把数字签名去掉,只保留启动信息。去掉数字签名并不会影响刷机程序的正常运行,因为数字签名不改变jar中的其它文件。

而com目录下是刷机程序的可执行文件,都是一些java的class程序,我们通常修改webOS Doctor时,是不去修改这个目录下的东西的。所以我们可以不去管它。

resources下面是刷机程序的资源。其中那些png是刷机程序里的图片,不用管它(如果你愿意也可以替换它们)。那些html是最终用户许可协议,如果你愿意的话,可以把它汉化。messages*.properties是刷机程序中的一些信息,可以按照这个格式写一份汉化版,这样刷机时选择中文,就会显示中文信息。但是要想选择中文,还需要修改一下languagePicker.properties,修改这个才能让你在刷机开始时选择语言。这些都改完了,刷机程序就可以彻底汉化了。

然后NovacomInstaller.pkg.tar.gz是Mac OS X上的驱动程序,NovacomInstaller_x64.msi和NovacomInstaller_x86.msi是Windows上的驱动程序,分别对于x64和x86系统。这些都不用管它。

recoverytool.config这个是非常重要的一个文件。

前面四行是跟版本信息有关的,可以根据喜好改。

BaseBuild是基础包,它指向webOS.tar这个文件,你可以修改成你自己喜欢的名字,但实在没这个必要。

CustomizationBuild这个是定制包,att的版本这个包名是att.tar,o2等欧洲版本这个包名是wr.tar,verizon版本这个报名是verizon.tar或verizon-world.tar。其实这个包名叫啥都行,可以自己改。

ForceModemUpdate决定是否升级MODEM模块,默认为true,改成false就不刷MODEM了。

SoftwareUpdateSite应该是指软件升级的站点,我们能看到的就是production这个选项,其它选项是什么我就不知道了。

在接下来的三项(AppSubmssion、LibCoreSubmssion、WcpSubmssion)是ROM中软件包的数量,这个不用管它。

DeviceType这个是机型,这个跟webOS.tar中的那个xml是对应的。不要乱改。除非你知道自己在做什么。

ApprovalBuildName是指这个ROM的编译版本,后面那一堆看似乱码的东西是可以解码的,例如Veer ATT 2.1.2的ROM中,这个值解码之后是:ps.palmws.com 'Release Build Name 1.3.2 (SDK935KRY93455)'。跟这个目录下的ps.palmws.com文件和其内容相对应。

ApprovalCharlieHash是指这个ROM的运营商,同样可解码。例如Veer ATT 2.1.2的ROM中,这个值解码之后是:./carrier_NAB.txt 'ATT'。

ApprovalMikeHash是指这个ROM的机型,同样可解码。例如Veer ATT 2.1.2的ROM中,这个值解码之后是:./model_Nova_ATT_Broadway.txt 'P160UNA'。

最后我们讲两个最重要的文件,那就是webOS.tar和另一个<carrier>.tar文件(<carrier>表示att、wr、verizon等)。关于<carrier>.tar我们下次再谈。这次先来详解一下webOS.tar。

webOS.tar是刷机的基本包。它里面包含了整个系统的内容。

其中,boot-<model>.bin是启动文件(其中<model>表示设备制式,例如pre/pre+是castle,pixi/pixi+是pixie,pre2是roadrunner,veer是broadway,pre3是mantaray,touchpad是topaz),它负责载入安装镜像nova-installer-image-<model>.uImage。

*umts*.tar,*cdma*.tar这些是MODEM模块。

*.tga是一些系统的图片。如果你想美好它们的话,可以对它们进行修改和替换。

上面这些文件我们通常不做修改。除非在跨运营商或跨机型移植ROM时,才会替换这些文件。跨运营商移植是比较常见的,替换MODEM模块之后再稍做些修改就可以了。跨机型移植难度较大,不知道是否有人真的尝试过,因为跨机型移植不是替换几个文件就能搞定的。

定制ROM时修改最多的是这三个文件:nova-cust-image-<model>.rootfs.tar.gz,installer.xml和<model>.xml。

nova-cust-image-<model>.rootfs.tar.gz这个里面就是我们手机上运行的系统的根分区的主要内容的压缩包。刷机最关键的部分就是把它来写入到手机的根分区。

installer.xml是里面是刷机的基本配置信息,我们通常不对它修改,最多就是修改ModemUpdater这个,去掉它就不更新MODEM了。

<model>.xml这个文件是非常重要的,里面包含了文件系统的分区信息,哪些数据写入到哪儿,键盘配置等等信息。这个文件我们通常也不会去修改它。最常见的修改就是键盘布局和对分区的调整的修改(例如增加分区,调整某些分区大小等)。而直接用MetaDoctor的选项来对该文件作这些修改更安全一些。

我们再回到nova-cust-image-<model>.rootfs.tar.gz这个文件的讨论上来。

nova-cust-image-<model>.rootfs.tar.gz是对根文件系统做的tar归档文件,然后用gzip进行了压缩。我们可以直接用tar命令将它展开。更方便的方法是借助MetaDoctor中的unpack-doctor或者unpack-rootfs。推荐使用unpack-doctor,这个更好用一些。

解压之后,我们就可以看到系统的大部分文件了。这里说大部分的意思是,有些系统文件不是在这里面直接包含的,而是在另一个<carrier>.tar中安装的,还有一些是在刷机之后第一次启动过程中动态生成的。我们先不管这么多。

在rootfs的根目录下,有一个md5sums.gz文件,解压之后里面是一个名为md5sums的文本文件。该文件记录了这个rootfs中包含的所有文件的md5sum值,如果你对rootfs里的任何文件作了修改,一定要更新md5sums中对应的md5sum值,否则刷机到82%时,刷机程序会提示你设备无法重启。

当然系统验证并不是简单的只看这个md5sums.gz文件。实际上这个rootfs下的内容基本上都是系统预安装好的包(ipk),而这些包的信息保存在/usr/lib/ipkg目录下。

这个目录下,status是保存了包的安装状态。

lists目录下有三个文件,oe-all,oe-<arch>,oe-<model>。其中<arch>是指构架,例如armv7,armv6,i386。<model>是指机型,上面已经介绍了,这里不再重复。

其中oe-all是可以完全移植到其它机器上的包,oe-<arch>是可以移植到相同构架机器上的包,而oe-<model>最为严格,只有相同的机型才能够移植。例如,如果你打算把pre3的系统移植到veer上来,pre3的oe-all和oe-armv7这两个里面的包应该都是可以顺利的那到veer上来的,而oe-mantaray里面的包则不能移植到veer上来,而应该保留veer上原有的oe-broadway的这些包,但这种杂交系统的做法是否真的不影响兼容性我倒是未曾作过验证和尝试。有兴趣并有能力的同学可以试试看。

info目录下就是每个包的控制文件了。其中:

*.control文件是每个包的基本信息文件。

*.list里面记录了每个包中都包含了哪些文件。

*.preinst是每个包安装之前要执行的脚本文件。这种脚本很少。

*postinst是每个包安装之后要执行的脚本文件。

*.prerm是每个包卸载之前要执行的脚本文件。

*.postrm是每个包卸载之后要执行的脚本文件。

*.md5sums是每个包的验证文件,在系统刷机之后,刷机程序也会来验证这些*.md5sums中记录的md5散列值,所以刷机之后的验证是一个双重的验证,因此要手工修改rootfs又要做到能够通过验证确实是非常不容易的,这也是目前国内的一些ROM定制团队定制的ROM无法通过刷机之后的验证,到82%就提示无法启动的主要原因。

几乎所有的文件都可以归入包中,有少数不在包中的文件也都是由预先安装的包因已经执行过postinst脚本所生成的。

所以,我们可以认为这个rootfs.tar.gz就是一组预先安装好的包的集合。搞清楚这个结构,我们后面修改ROM就容易多了。