浏览模式: 标准 | 列表
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月
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激活的功能。

11月
04

WebOS ROM修改入门教程