浏览模式: 标准 | 列表
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就容易多了。

11月
25

免发激活短信

可能很多人把WIFI激活和免发激活短信弄混(包括我最开始也有弄混),实际上它们之间没有任何关系。

WIFI激活并不能免发激活国际短信,免发激活国际短信也不一定非要WIFI激活。

如果只是向上文中那样改成可WIFI激活,并不能免发激活短信。

那怎么才能免发激活短信呢?

最简单就是同时开启BYPASS_ACTIVATION和我们上篇中添加的WIFI_ACTIVATION选项。并在刷机时不要插卡(不插卡能不能成功激活Profile还不好说,反正我没成功过,不知是当时的网络问题还是不插卡就不能注册或登录Profile),或者插一张废卡(这个有人测试成功过),即可避免发激活短信了。

但上面的方法是有缺陷的,首先你不能用2G、3G这些传统的方法来注册或登录Profile了。然后就是进入系统之后,手势教程也是不能用的。BYPASS_ACTIVATION可能还会带来一些其它的副作用。所以,我们不推荐用上面的方法。

下面就来说一下本人发现的免发激活短信的方法。

虽然发现该方法费了好多工夫,但该方法在实现上却非常简单。在com.palm.app.firstuse的app/models/account-service.js文件中有一个方法:postLoginSettings。这个方法是发送激活短信的罪魁祸首。

它是在哪儿被调用的呢?

它在两处被调用。一处是在com.palm.app.firstuse的app/controllers/restorecomplete-assistant.js的restoreDone方法中被调用的。另一处是com.palm.app.firstuse的app/controllers/status-assistant.js的setup方法中被调用的。

restoreDone这个方法显然是在Profile被恢复的之后,按了完成按钮之后执行的。而status-assistant.js的情况稍微复杂一点。

但是不管是在哪儿被调用,他们都有一个共同点,就是这个方法不管是调用成功还是调用失败都不影响后续正常执行,因为它的成功回调和失败回调除了日志内容不同以外,其它的主要操作都是一模一样的。也就是说完全可以跳过这个方法的执行。

最简单的跳过方式当然就是直接修改postLoginSettings方法,而不是修改调用的地方。那该如何修改呢?最简单的方法就是把原来的对服务的调用注释掉,然后改为直接调用回调方法:

JavaScript代码
 
  1. postLoginSettings: function(AccountLocales, successCallback, errorCallback, sceneController) {  
  2.     /*return sceneController.serviceRequest(NovaAccountService.identifier, { 
  3.         method: 'postLoginSettings', 
  4.         parameters: {"firstTime": true, 'language':AccountLocales.language, 'country':AccountLocales.country}, 
  5.         onSuccess: successCallback, 
  6.         onFailure: errorCallback 
  7.     }, true);*/ successCallback({ "returnValue"true"errorCode": 0, "errorString""success"});  
  8. },  

 注意,这里我们没有增加行数,没有减少行数,为的是避免跟其它的补丁产生冲突。这样修改之后,对于restoreDone中的调用可以完美实现不发激活国际短信了。但是对于新注册或者原来的帐号没有备份的情况下,却会发生点击完成无限转圈圈的问题。这是什么原因呢?

仔细分析我们会发现,在status-assistant.js的setup最后有两行代码:

JavaScript代码
 
  1. this.postLoginRequestComplete = false;  
  2. this.provisioningRequestComplete = false;  

而在对postLoginSettings的回调方法中会将this.postLoginRequestComplete设置为true。并在最后的saveDoneState方法中对这两个标志检测是否都为true,只有都为true时才会保存设置并重启手机。

而上面的修改我们把正常的回调改成了直接调用回调方法,结果就是回调方法被提前执行了,之后又执行到setup底部时,this.postLoginRequestComplete又被重新设置为了false,导致这个标志永远都无法变成true,也就导致了无法重启,一直转圈圈了。

所以,我们应该把直接调用回调方法改成延迟回调,最简单的方法就是用setTimeout方法,下面是正确的修改:

JavaScript代码
 
  1. postLoginSettings: function(AccountLocales, successCallback, errorCallback, sceneController) {  
  2.     /*return sceneController.serviceRequest(NovaAccountService.identifier, { 
  3.         method: 'postLoginSettings', 
  4.         parameters: {"firstTime": true, 'language':AccountLocales.language, 'country':AccountLocales.country}, 
  5.         onSuccess: successCallback, 
  6.         onFailure: errorCallback 
  7.     }, true);*/setTimeout(function() {successCallback({ "returnValue"true"errorCode": 0, "errorString""success"});}, 3000);  
  8. },  

如此修改之后,便实现了完美的任何环境下激活都免发国际短信了。

我们可以把这个修改合并到前面的WIFI激活补丁中去,这样就可以直接用MetaDoctor来实现对ROM的完美修改了。

11月
04

WiFi激活

WiFi激活不是本人首创,该方法在WebOS还只是1.0.2的时候,就已经被开发出来了,并被记录在了 http://www.webos-internals.org/wiki/Patch_webOS_Bypassing_Activation 上。

但是上面写的都是手工修改的方法,做起来比较麻烦,用MetaDoctor就会简单的多:

第一步,在patches目录下找到你需要修改的版本的目录,然后在里面创建一个空的patch文件,比如叫wifi-activation.patch。

第二步,用MetaDoctor的scripts目录下面的unpack-doctor或者unpack-rootfs把WebOS Doctor解开。

第三步,找到/usr/palm/applications/com.palm.app.firstuse/app/controllers/firstuse-util.js,复制一份,比如叫firstuse-util.new.js,然后对其修改保存。

第四步,运行diff –u firstuse-util.js firstuse-util.new.js > firstuse-util.patch,生成补丁。

第五步,同样方法修改并生成account-service.patch。

第六步,把这两个patch里的内容复制到patches目录下创建的那个wifi-activation.patch文件中,每个文件开头那两行的格式,可以按照bypass-activation.patch里面的格式改一下(我没试过不改可不可以用)。

最后,修改Makefile,在其中加上WIFI_ACTIVATION这个选项,修改方法可以参考它原来的BYPASS_ACTIVATION的代码,具体修改那几行这里就不列出来了。 上面的说明中,细心的读者也许会发现,我把Wiki里面关于app-assistant.js的修改给忽略了。不是我忘记了,而是故意的,下面就来解释为何忽略。关于app-assistant.js的修改其实就是bypass-activation.patch做的事情,不同版本的webOS的bypass-activation.patch并不相同,所以按照那个修改并不是最好的方法,最好的方法是直接在Makefile中把BYPASS_ACTIVATION这一项打开就一样了。另外,WIFI_ACTIVATION跟BYPASS_ACTIVATION并没有直接的关系,只是打开BYPASS_ACTIVATION之后,你可以在刷机之后不用插卡,但是不能用其它方式激活,只能用WIFI激活。而不打开BYPASS_ACTIVATION的话则必须插卡,但是除了可以用WIFI激活意外,也可以用2G、3G方式激活。

好了,现在运行一下MetaDoctor的make命令,你的带WIFI激活的ROM就生成了。

刷机之后,在紧急拨号菜单里就可以打开WIFI设置页了。

但这个WIFI设置有一点点小问题,就是设置完成之后,你会发现无法退出这个设置界面,要想继续激活的过程,就需要用“方块+Sym+R”来重启手机才能继续。

那如何才能让让WIFI设置界面可以在设置完成之后正常退出呢?这个思路可以参考紧急拨号界面。

在account-service.js这个文件中,有一个方法是用来打开紧急拨号界面的,我们来看一下它的代码:

JavaScript代码
 
  1. makeEmergencyCall: function (emergencyNumber) {  
  2.     Mojo.Log.info("Emegerncy call to phone app -------------------------------------------------: ",emergencyNumber);  
  3.     return new Mojo.Service.Request("palm://com.palm.applicationManager/open", {  
  4.            parameters: {  
  5.                id: 'com.palm.app.phone',  
  6.                params: {  
  7.                    "prefill": emergencyNumber,  
  8.                 "firstuse":true,  
  9.                 "locale": Account.language + '_' + Account.country   
  10.                }  
  11.            }  
  12.        });  
  13.       
  14. },  

这段代码中,我们可以看到firstuse和locale这两个参数,也就是说紧急拨号调用的就是电话程序,只不过是传入了一个firstuse参数让其可以作特殊处理(locale则是可以在调用它时设置语言)。所以,我们在紧急拨号界面下可以看到取消按钮,而普通情况下打电话则没有这个按钮。

所以,我们也可以给WIFI设置程序增加一个firstuse参数,如果要让他也能根据调用环境设置语言,我们也可以给它增加一个locale参数,不过com.palm.app.wifi本身是不支持这两个参数的,因此我们要对它进行修改。下面是对com.palm.app.wifi相关文件进行的修改:

JavaScript代码
 
  1. --- .orig/usr/palm/applications/com.palm.app.wifi/app/controllers/app-assistant.js  
  2. +++ /usr/palm/applications/com.palm.app.wifi/app/controllers/app-assistant.js  
  3. @@ -9,6 +9,16 @@  
  4.     var dashboardStage = this.controller.getStageProxy('wifidashboard');  
  5.     var networkListStage = this.controller.getStageProxy('networklist');  
  6.    
  7. +   if (params) {  
  8. +       this.firstuse = PalmSystem.isMinimal || params.firstuse === true;  
  9. +       //If the First Use passes the locale different than the current one, change the Mojo.Locale(Framework)  
  10. +       if (this.firstuse) {  
  11. +           if (params.locale && params.locale.length === 5 && params.locale != Mojo.Locale.current) {  
  12. +               Mojo.Locale.set(params.locale);  
  13. +           }  
  14. +       }  
  15. +    }  
  16. +  
  17.     if (networkListStage)  
  18.     {  
  19.         Mojo.Log.info("Wifi launch reactivate");  
  20. --- .orig/usr/palm/applications/com.palm.app.wifi/app/controllers/networklist-assistant.js  
  21. +++ /usr/palm/applications/com.palm.app.wifi/app/controllers/networklist-assistant.js  
  22. @@ -3,6 +3,7 @@  
  23.  var list_label_toggle_off = $L("Off");  
  24.  var list_label_list_other_network = $L("Join other network");  
  25.  var list_label_list_network = $L("Join network");  
  26. +var list_label_done_button = $L("Done");  
  27.    
  28.  var list_label_status_associating = $L("Connecting......");  
  29.  var list_label_status_associated = $L("...Connecting...");  
  30. @@ -70,7 +71,7 @@  
  31.              Mojo.Log.info("initialize(): called via wifi preferences with params - " + Object.toJSON(params));  
  32.              this.bRunViaPreferencesMenu = true;  
  33.              this.params = params;  
  34. -        } else if (params.hotspot) {  
  35. +        } else if (params.hotspot || params.firstuse) {  
  36.              this.params = params;  
  37.          }  
  38.          this.findNetworksHandle = undefined;  
  39. @@ -170,6 +171,17 @@  
  40.              }  
  41.          });  
  42.    
  43. +        this.doneButtonModel = {buttonLabel: list_label_done_button};        
  44. +        this.controller.setupWidget('doneButton', {type:Mojo.Widget.activityButton}, this.doneButtonModel);  
  45. +        this.doneButton = this.controller.get('doneButton');  
  46. +        if (this.params && this.params.firstuse) {  
  47. +            this.doneButton.style.display = "block";  
  48. +        }  
  49. +        else {  
  50. +            this.doneButton.style.display = "none";  
  51. +        }  
  52. +        Mojo.Event.listen(this.doneButton, 'mojo-tap'this.closeAllStages.bind(this))  
  53. +  
  54.          timing.pause('scene#networklist#setup');  
  55.  //        Mojo.Log.info("setup(): leaving");  
  56.      },  
  57. @@ -976,6 +988,10 @@  
  58.             template: 'joinwapicerts/joinwapicerts-scene',  
  59.             assistant: new JoinwapicertsAssistant(this, item)  
  60.          });  
  61. +    },  
  62. +  
  63. +    closeAllStages: function() {  
  64. +        this.controller.stageController.getAppController().closeAllStages();  
  65.      }  
  66.    
  67.  });  
  68. --- .orig/usr/palm/applications/com.palm.app.wifi/app/views/networklist/networklist-scene.html  
  69. +++ /usr/palm/applications/com.palm.app.wifi/app/views/networklist/networklist-scene.html  
  70. @@ -18,4 +18,5 @@  
  71.      <div id="wifiOffText" style="display:none;" class="palm-text-wrapper">  
  72.             <div class="palm-body-text" x-mojo-loc=''>Wi-Fi is turned off.</div>  
  73.      </div>  
  74. +    <div x-mojo-element="Button" style="display:none;" id="doneButton"></div>  
  75.  </div>  

其实很简单就是增加一个Done的按钮,这个按钮在com.palm.app.wifi启动时,如果带有firstuse这个参数就设置为可见,而这个按钮的用处就是关闭WIFI设置程序。另外,开头还会根据locale参数来设置一下语言。

那现在我们在com.palm.app.firstuse里面再调用它,只要带上这两个参数就可以了。接下来我们可能还希望在刷机第一次启动时,选择语言之前就能进行WIFI配置。最简单的方法当然就是在设置语言的页面里面也调用这个打开WIFI设置程序的方法咯。但如果你真的这样做的话,会发现你并看不到WIFI设置程序。原因是最开始执行时,被放在后面了,只有当它完全启动之后,再调用一次,他才会显示到前面来。但是我们不知道它需要多久才能启动完毕,所以我们设置几个定时器,每隔一段时间调用一次就可以啦:

JavaScript代码
 
  1. --- .orig/usr/palm/applications/com.palm.app.firstuse/app/controllers/language-assistant.js  
  2. +++ /usr/palm/applications/com.palm.app.firstuse/app/controllers/language-assistant.js  
  3. @@ -34,6 +34,7 @@  
  4.           
  5.         // Listen to ## key presses  
  6.         this.controller.listen(this.controller.sceneElement, Mojo.Event.keydown, FirstUseUtil.handleKeyPressEvent.bind(this));  
  7. +       AppAssistant.accountService.enableWifi();  
  8.     },  
  9.       
  10.     powerDownDevice: function () {  
  11. --- .orig/usr/palm/applications/com.palm.app.firstuse/app/controllers/firstuse-util.js  
  12. +++ /usr/palm/applications/com.palm.app.firstuse/app/controllers/firstuse-util.js  
  13. @@ -197,11 +197,13 @@  
  14.         }  
  15.         else{  
  16.             labels = [{label: $L('Emergency call'), command: 'emergency-call'},   
  17. +               {label: $L('Enable WiFi'), command: 'enable-wifi'},  
  18.                 {label: $L('Cancel'), command: 'cancel'}  
  19.                 ];  
  20.             if(FirstUseUtil.customerCareNumber && FirstUseUtil.customerCareNumber.length > 0) {  
  21.                 labels = [{label: $L('Emergency call'), command: 'emergency-call'},   
  22.                     {label: $L('Call customer service'), command: 'customer-care'},  
  23. +                   {label: $L('Enable WiFi'), command: 'enable-wifi'},  
  24.                     {label: $L('Cancel'), command: 'cancel'}  
  25.                 ];  
  26.             }  
  27. @@ -220,6 +222,8 @@  
  28.             Mojo.Log.info("----------------- FirstUseUtil.carrierName ----------------", FirstUseUtil.carrierName());  
  29.             var carrierName = Mojo.Environment.DeviceInfo.carrierName || (FirstUseUtil.carrierString && typeof FirstUseUtil.carrierString === "string" ? FirstUseUtil.carrierString.toUpperCase() : "");  
  30.             AppAssistant.accountService.callCustomerCare(FirstUseUtil.customerCareNumber, $L(carrierName));  
  31. +       } else if (value == "enable-wifi") {  
  32. +           AppAssistant.accountService.enableWifi();  
  33.         } else if(value == "exit"){  
  34.             if (isDemoMode){  
  35.                 new Mojo.Service.Request('palm://com.palm.oddService', {  
  36. --- .orig/usr/palm/applications/com.palm.app.firstuse/app/models/account-service.js  
  37. +++ /usr/palm/applications/com.palm.app.firstuse/app/models/account-service.js  
  38. @@ -408,6 +408,33 @@  
  39.         });  
  40.     },  
  41.       
  42. +   enableWifi: function() {  
  43. +       var method = function() {  
  44. +           return new Mojo.Service.Request("palm://com.palm.applicationManager/open", {  
  45. +               parameters: {  
  46. +                   id: 'com.palm.app.wifi',  
  47. +                   params: {  
  48. +                       "firstuse"true,  
  49. +                       "locale": Account.language + '_' + Account.country  
  50. +                   }  
  51. +               }  
  52. +           });  
  53. +       };  
  54. +       var t1 = setTimeout(function() {  
  55. +           clearTimeout(t1);  
  56. +           method();  
  57. +       }, 8000);  
  58. +       var t2 = setTimeout(function() {  
  59. +           clearTimeout(t2);  
  60. +           method();  
  61. +       }, 4000);  
  62. +       var t3 = setTimeout(function() {  
  63. +           clearTimeout(t3);  
  64. +           method();  
  65. +       }, 2000);  
  66. +       return method();  
  67. +   },  
  68. +  
  69.     makeEmergencyCall: function (emergencyNumber) {  
  70.         Mojo.Log.info("Emegerncy call to phone app -------------------------------------------------: ",emergencyNumber);  
  71.         return new Mojo.Service.Request("palm://com.palm.applicationManager/open", {  

经过这样的修改,我们就可以实现完美的开机WIFI设置了。

上面两段加起来就是WIFI激活的补丁了。我们用这个补丁来替换最开始那个wifi-activation.patch就可以啦。

不过我们还需要作一点小小的工作,那就是要把com.palm.app.wifi这个包的处理部分也要加入到Makefile中,我们可以在Makefile中寻找这段代码:

  1. ifeq (${ADD_EXTRA_CARRIERS},1)  
  2.     OLDIPKGS += pmcarrierdb  
  3. endif  

然后再它前面加入:

  1. ifeq (${WIFI_ACTIVATION},1)  
  2.     OLDIPKGS += com.palm.app.wifi  
  3. endif  

就可以啦,接下来的工作,Makefile就可以正确处理了。

最后我们运行:

make DEVICE=veer CARRIER=att all

就可以生成带有WIFI激活功能的ROM了。

11月
04

WebOS ROM修改工具

工欲善其事,必先利其器。所以,我们要先把用于修改WebOS ROM的工具找好,其实工具不多,下面先列一下这些工具:

  1. Linux(例如Ubuntu或Debian)或Mac OS X
  2. JRE
  3. Palm SDK
  4. MetaDoctor

接下来,我们就有细细讲解了。

Linux或Mac OS X

必须的!

Windows不行,因为后面要介绍的主力工具MetaDoctor不支持Windows,就算在Windows上装了Cygwin也不支持。至于是不是真是这样,我倒没有亲自验证过,www.webos-internals.org上是这样写的。具有科学态度的同学如果有时间,可以自己验证一下。

用Linux的话,它的shell是现成的,定制ROM需要到一些命令和工具在系统装好之后也都有了,比如tar、ar、unzip、make等等。另外,推荐用Ubuntu或Debian。因为就算缺少什么命令没有安装,直接用apt-get或者图形界面的软件包管理器安装就可以啦,非常方便。

用Mac OS X的话,需要把XCode一起安装一下,虽然用不到XCode本身,但需要用到它带的一些开发用的工具。另外,还需要安装MacPorts,它也依赖XCode。所以,用Mac OS X的话,比起用Ubuntu或Debian来还是比较麻烦的。

至于这两个系统怎么用这里就不再详解了,否则就跑题了。

JRE

必须的!

后面介绍的工具需要调用它来完成一些工作。

这个怎么安装,干啥用的也不介绍了。我想用webOS手机的同学应该没有不知道它的。而且资料多的很,就算真的不了解的同学也可以自己去查嘛。我就不再废话了。

Palm SDK

必须的吗?不是。它是webOS的开发包,虽然我们并不是要用它来做开发,但它在我们用于定制ROM的调试过程中会很有用。另外,MetaDoctor中也有调用它的地方,不过也不是在定制ROM的环节上。但我们装了它总没坏处。

MetaDoctor

定制WebOS ROM的主力工具。虽然它只是几个不算怎么长的脚本的集合,但是有了它,可以让你在定制ROM上做到事半功倍,比纯手工打造要方便太多了。当然,它本身内置的脚本并不一定能满足你所有的要求,但你可以扩展修改它,至于怎么扩展修改,我们下一节再举例说明。

现在先介绍它的基本功能。

http://www.webos-internals.org/wiki/Application:MetaDoctor上有关于MetaDoctor的一些英文介绍,大致涵盖了安装,基本运行方法,和一些杂七杂八的东西。所以,这些也不详细讲解了,不过为了保持文章完整性,还是要大体说一下。

安装

很简单。

只有Windows的同学,需要安装一下Linux,推荐是Ubuntu最新版。安装方式上面的英文介绍里说的是用Wubi方式,其实装在虚拟机里也一样用。

然后呢,就是安装git,安装jre,之后用git来把meta-doctor从服务器上下载下来,这样你就有了。

在Mac OS X上也差不多,不过要先安装XCode、再安装MacPorts,再用MacPorts安装md5sha1sum、gnutar、git-core、wget、gpatch这些基本工具,最后也是用git把meta-doctor从服务器上下载下来。

目录结构

从服务器上下载的meta-doctor目录下面有一个Makefile和三个目录。

这个Makefile是定制ROM的最关键的主文件,后面详解其中的关键。先说说那三个目录:

patches目录

这个目录下,保存的是一些补丁。

其中carriers目录下是某些运营商的网络配置的定制文件,doctor目录下是对刷机工具的几个补丁文件。

webos-x.x.x这些目录下面是对具体版本的ROM的一些补丁,自带的里面只有一个bypass-activation的补丁。

这些文件都是供Makefile调用的,如果有需要我们也可以把自己写的补丁放在它下面,然后对Makefile做相应修改之后,我们自己的补丁也就可以用了。

hashes目录

这个目录下,保存的是一些运营商(carriers)和手机制式(models)的散列数据。用于设置自定义ROM的运营商和手机制式用。它们也是供Makefile调用的。

scripts目录

这个目录下保存了很多宝贝脚本。

一部分是供Makefile调用的,一部分是单独使用的,还有一部分是例子。

这里面replace-md5sums.py是供Makefile调用的。

preware-ca-bundle.crt和preware-install.db也是供Makefile调用的,不过已经过时,没啥用了。

decode-hashes是解码刷机文件中recoverytool.config这个文件里面那些运营商、手机制式等Hash值用的工具。

encode-hash是教你如何实现自定义运营商、手机制式等Hash值的编码的一个例子。

unpack-doctor可以帮你把刷机的doctor的jar分解成目录和文件的工具。

unpack-rootfs跟unpack-doctor功能类似,但它只分解rootfs。

WebOS-Internals.tga是Makefile用来替换手机启动时的第一个启动画面的文件。你也可以把它替换掉。

剩下的那些xxx-xxx-1.4.5.1是用来自制某些运营商发布的某些制式的手机的1.4.5.1的刷机ROM的。xxx-xxx-2.1.0当然就是定制2.1.0的刷机ROM的咯。关于这些2.1.0的脚本的使用方法,可以参见:http://www.webos-internals.org/wiki/WebOS_2_Upgrade。因为这不是本文的重点,这里就不做更进一步的介绍了。

Makefile

这个文件才是重点要介绍的。

该文件开头有许多选项,这些是基本功能定制的开关。下面先把这些选项作个简单说明。

BYPASS_ACTIVATION

该选项的功能说明是去掉刷机后第一次启动时的激活过程。而实际上它的作用仅仅是让第一次启动的运营商网络变得无效,以至于无法激活。另外,它还有一个作用就是让机器不检查Sim卡状态。这时,你就是插一张坏卡也能开机,不插卡也能开机。但不能激活你就进不了系统啊。那怎么办呢?

如果是2.x的系统,那好办,可以在紧急拨号界面里,输入#*3386633#来启动开发者模式开关程序,打开开发者模式之后重启,第二次启动就自动进入系统了。

但如果是1.x的系统,那就麻烦了。因为上面的方法对1.x系统不适用啊。那该怎么办呢?

一种办法当然就是我们下一节介绍的WIFI激活。另一种方法则是跳过第一次启动时运行的First Use程序,也就是第二个选项。

BYPASS_FIRST_USE_APP

这个选项做的事情正如上面所说,是跳过第一次启动时运行的First Use程序,如何实现的呢?

他在rootfs(就是ROM的根分区)的/var/luna/preferences下建了两个空文件,一个是ran-first-use,另一个是first-use-profile-created,这样就可以欺骗系统在第一次运行时就认为已经运行过firstuse程序了,所以,第一次运行就进入了桌面。

2.x系统的firstuse程序跟手势教程是同一个,想要激活profile,直接运行手势教程就好了。所以就不存在什么问题。但对于1.x的系统来说,firstuse程序默认是不可见的,所以进入桌面之后,想再激活profile就难了。这一点其实开发meta-doctor的大婶们早就想到了。所以,这个选项做的第二件事情就是将/usr/palm/applications/com.palm.app.firstuse下面的appinfo.json和它的资源目录下所有语言的appinfo.json中的visible选项从false改为true。

所以,一般要实现免激活的ROM,只要打开上面两个选项就好了。

注意:BYPASS_FIRST_USE_APP这个选项在修改程序可见性时,有点小问题。使得它跟1.x的advanced configuration for app laucher这个补丁有冲突,使其无法正确安装。解决方法有很多,比如用户安装advanced configuration for app laucher前先把原始的appinfo.json文件替换回去。或者把Makefile中关于appinfo.json的修改的那一段代码注释掉。更多方法大家可以自己琢磨。

ENABLE_DEVELOPER_MODE

第三个选项是默认打开开发者模式,这样在第一次启动时,WQI就可以跟手机连接啦。这个选项打开是安全的。它的实现也很简单,就是在根分区的/var/gadget目录下建立一个novacom_enabled的空文件。

该选项只对刷机后未运行删除应用程序和数据(或完全清除)前有效。如果日后在系统的“重设选项”中选择了任何上述按钮的话,则该文件也会被清除,这样系统启动时,再次进入First Use界面时,开发者模式就关闭了。

ENABLE_BETA_FEEDS、ENABLE_ALPHA_FEEDS

第四个选项是针对preware的,该选项打开之后,可以允许preware载入测试版本的程序和补丁列表。

这两个选项的实现是在rootfs(根分区)的/var/preferences/org.webosinternals.preware目录下面建立enable-beta-feeds或enable-alpha-feeds这两个空文件。所以它们并不依赖于是否在ROM中集成preware。

INSTALL_SSH_AUTH_KEYS

这个选项不用于定制通用ROM,仅用于定制个人使用的ROM时使用。它的作用是将SSH的authorized_keys文件导入到设备的用户目录下,这样当在设备上安装了ssh监听程序后,就可以直接从自己的电脑上通过ssh来登录到设备上了。

INSTALL_WIFI_PROFILES

这个选项跟上面一样不能用于定制通用ROM,仅用于定制个人使用的ROM。它的作用是让用户刷机之后第一次启动就可以连接上WiFi,这个功能听上去很神奇,但如果你知道该怎么做才能实现时,你就会觉得很没用了。你需要先把现在设备上设置好的WiFi配置文件(/var/preferences/com.palm.wifi/ prefsDB.sl)复制到自己PC(Linux)上,路径是:~/.ssh/com.palm.wifi.prefsDB.sl 或 to ./config/com.palm.wifi.prefsDB.sl,然后开启这个选项后,在运行make时,meta-doctor会把这个文件放到你的ROM中的那个目录下。而每个人的WiFi配置各不相同,所以它不是一个通用的选项。

AUTO_INSTALL_PREWARE

这个选项的本意是系统启动后,自动通过网络安装preware。但很不幸,这个选项已经不好用了。所以你只能在注释中看到它。在下面的主要开关列表部分你并看不到它的身影。所以如果你想实现在ROM中集成preware,不要使用这个选项,后面我会介绍如何集成软件到ROM中。

DISABLE_UPLOAD_DAEMON

这是一个很有用的选项。官方ROM里有个自动上传数据到PALM服务器的功能。这个自动上传数据的功能会在后台悄悄地自动地上传系统或应用程序崩溃的相关调试信息,用户的GPS信息,每个应用程序所使用的数据,以及它们的运行时长。关掉它可以节省大量的流量。

这个选项的实现很简单,就是把/usr/bin下面的uploadd、contextupload、rdxd这三个程序的可执行权限去掉了。这样当你真的需要恢复时,只要改一下权限就好了。所以即使在刷机之后,用同样的方法也可以关闭上传功能。

DISABLE_UPDATE_DAEMON

这个是用来关闭升级服务的。通常用于定制比较旧的,官方不再提供升级的ROM时使用,在定制较新的ROM时,如果开启这个选项,对于不懂下面这段话的用户会影响后续的正常升级。

这个选项的实现跟上面那个选项差不多,它是把/usr/bin目录下的UpdateDaemon和OmaDm这两个程序的可执行权限去掉了。所以如果到时候真的系统真的有升级,用户其实也是可以通过修改这两个文件的权限来恢复升级服务功能的。同样,如果没有开启这个选项,用户在刷机后,也可以用这个方法来关闭升级服务。

DISABLE_MODEM_UPDATE

该选项的作用是禁止刷写设备的MODEM。因为对于同一款手机的同一个版本的ROM来说,MODEM通常是不会单独升级的,只有大版本升级时,MODEM才会有可能升级(例如从1.4.5到2.1),通常忽略MODEM刷写是安全的。而且它可以节省刷机的时间,刷写MODEM时,大约需要4-5分钟,禁止刷写MODEM可以节省将近1/3的刷机时间。不过,要是为了保险的话,这个选项还是不要开启的好。

它的实现方式是修改webOS.tar里面的installer.xml,将其中ModemUpdater开头的这行删除掉。

ENABLE_USB_NETWORKING

该选项的作用是激活USB网络功能。因为这个功能需要电脑上单独安装驱动。所以一般不打开。

它的实现方式是在/var/gadget下建立一个名为usbnet_enabled的空文件。

REMOVE_MODEL_CHECK

该选项的作用是移除手机型号检查。例如将P130UNA的ROM刷到P130UEU的设备上时,可以使用该选项。但你如果把P101UNA刷到P121UNA的设备上时,也是可以使用该选项的,但这真的很有风险,因为没有人可以保证任何两个不同型号的手机的ROM一定会兼容,所以,如果你不知道你在干什么,就不要随便打开这个选项。

这个选项的实现其实很简单,将doctor里面的resources目录下的recoverytool.config文件中的ApprovalMikeHash那一行删除就可以啦。

REMOVE_CARRIER_CHECK

该选项的作用是移除运营商检测。例如你打算将ATT的ROM刷到O2上(当然因为这两个手机的型号也不同,所以你还需要把上面那个选项也打开),你就应该打开这个选项。不过它会带来很多副作用,因为开启这个选项之后,刷机时将不会为你安装运营商提供的系统级的ipk,而这些ipk有几个还是相当重要的,完全忽略安装的情况下,你会得到一个很纯的系统,而这个很纯的系统还会有些功能上的缺失,例如壁纸、铃声设置不能保存,应用商店可能无法使用,也无法升级程序等等。所以,通常我们也不会打开这个选项。要实现刷写其它运营商更高版本的ROM,我们其实还有更好的方法。

这个选项的实现比上面的选项稍微复杂些,除了将doctor里面的resources目录下的recoverytool.config文件中的ApprovalCharlieHash和CustomizationBuild这两行删除外,对正常的关于运营商提供的安装包的操作也都忽略不做了。这个功能其实没啥用,所以就不做更详细解释了。如果大家感兴趣,还是自己读代码吧。

CHANGE_KEYBOARD_TYPE

这个选项很有用,主要用于给德国键盘、法国键盘的手机替换为正键键盘时制作ROM使用。它有三个选项,z表示QWERTY键盘,y或者y1表示QWERTZ键盘,w1表示AZERTY键盘。

它是通过修改webOS.tar中的intaller.xml以外的另一个xml文件实现的,那个文件的名字对于不同的手机有所差别。它是在这个文件的tokens这个段中,插入<Val name="KEYoBRD" action="overwrite" value="${CHANGE_KEYBOARD_TYPE}"/>这个选项来实现的,其中${CHANGE_KEYBOARD_TYPE}就是上面列出的三个选项值。

ADD_EXTRA_CARRIERS

这个选项是用来增加运营商网络配置信息的,还记得上面提到的patches/carriers目录吗?

这个选项就是将这个目录中的文件信息都加到根分区的/etc/carrierdb/carrierdb.json文件中,这样就可以实现自定义网络运营商的网络参数了。虽然这个选项的注释中说仅对2.0.0系统有效,其实是对2.0.0及其以上的版本都有效。只不过我们用不到,因为中国移动、中国联通和中国电信这三大运营商的配置早就写在这个配置文件里了。根本不需要我们去添加,这也是我们为什么拿到手机,插卡就能上网的主要原因。

VAR_PARTITION_SIZE

该选项可以让你更改/var分区的大小。这个分区用于保存一些用户数据,不过它下面还有几个目录实际上也是独立的分区都有自己固定的大小,单纯扩大/var分区不能够改变那些目录的空间大小,比如/var/log,/var/db,/var/file-cache等。

该选项的实现也是通过修改webOS.tar中的那个xml文件实现的。

SWAP_PARTITION_SIZE

该选项可以让你更改交换分区的大小。实现方式同上。

EXT3FS_PARTITION_SIZE

该选项可以增加一个/media/ext3fs的分区。安装ubuntu、debian、android到webOS中时,创建这样一个分区可以让系统更快速稳定一些(跟没有创建这个分区时比,不是跟没有安装上述系统时比)。实现方式同上。

总结

上面的修改中,有些修改涉及到根分区下的文件,这些文件中有些不属于某个包(ipk),添加之后只需要计算它的md5值,然后将该值增加到根分区根目录下的md5sum.tar中就可以啦。而有些文件是属于某个包的,对这些文件,在修改之后除了需要重新计算md5值,并将它写入根分区下的md5sum.tar中以外,还需要将该值写入到该包的md5sum校验文件中。另外,因为它所在包的md5sum校验文件也被修改过,因此它所在的包的md5sum校验文件也要重新计算md5值,并写入根分区总的md5sum校验文件包中。如果要在包中增加或删除文件,过程要更复杂,除了修改md5文件,还需要修改list文件和更新list文件的md5值。这个过程不但看上去非常复杂,手动作起来也确实繁琐,好在meta-doctor都帮我们做好了。所以我们借助meta-doctor这个工具,比我们纯手工修改定制ROM还是要方便的多的。

但是对recoverytool.config,webOS.tar下的两个xml文件的修改,手工修改可能比用meta-doctor更方便灵活一些。但手工修改难免会出错,所以风险也要大一些。修改ROM是个细心的活,一定要小心再小心。

meta-doctor的功能远不止上面介绍的这么点。其实它还提供了好多的选项让你更细化的对ROM做定制,只是这些选项并没有列在Makefile头顶的列表里罢了。如果你仔细读源码,你会发现很多有趣且有用的选项。另外,如果现有的功能真的不够用,你也可以通过修改Makefile来自己添加新的选项予以扩展。下一节我们就来讲如何通过修改meta-doctor来实现WIFI激活的功能。