<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://blog.zlion.top/</id>
  <title type="text">Shiyan</title>
  <subtitle type="text">Stay Free</subtitle>
  <updated>2026-09-22T00:00:00.000Z</updated>
  <author><name>Shiyan</name></author>
  <link rel="alternate" href="https://blog.zlion.top/"/>
  <link rel="self" href="https://blog.zlion.top/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://blog.zlion.top/posts/firefly-pages-cms-setup/</id>
      <title type="text">Firefly 博客接入 Pages CMS：浏览器里写文章，不用本地环境</title>
      <published>2026-09-12T00:00:00.000Z</published>
      <updated>2026-09-12T00:00:00.000Z</updated>
      <author><name>世琰</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/firefly-pages-cms-setup/"/>
      <summary type="text">给 Firefly（Astro）博客仓库放一份 .pages.yml，接入 Pages CMS 后在浏览器里就能写文章、传图片、改独立页面，保存即提交 GitHub 并自动部署；附完整配置与踩坑记录</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档面向使用 Firefly（Astro）主题、仓库托管在 GitHub 的博主，涵盖 Pages CMS 的接入步骤、<code>.pages.yml</code> 逐段讲解与踩坑记录。
<strong>适用主题：</strong> Firefly（Astro 内容集合架构，其他 Astro 博客同样适用）
<strong>适用平台：</strong> <a href="https://app.pagescms.org" target="_blank">app.pagescms.org</a>（浏览器内操作，无需本地环境）</p></blockquote>
<hr />
<section><h2>一、Pages CMS 是什么<a href="#一pages-cms-是什么"><span>#</span></a></h2><p><a href="https://pagescms.org" target="_blank">Pages CMS</a> 是一个架在 GitHub 仓库之上的在线内容管理后台：登录后选好仓库和分支，它把仓库里的 Markdown 文章渲染成可视化的编辑界面，写完点保存就是一次 Git 提交。</p><p>对静态博客来说，这条链路非常舒服：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>浏览器里写作</p></span></div><div><span><p>Pages CMS 保存</p></span></div><div><span><p>GitHub 自动 commit</p></span></div><div><span><p>部署平台构建</p></span></div><div><span><p>网站更新</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>浏览器里写作</p></span></div><div><span><p>Pages CMS 保存</p></span></div><div><span><p>GitHub 自动 commit</p></span></div><div><span><p>部署平台构建</p></span></div><div><span><p>网站更新</p></span></div>
</div></div></div><p>不用开电脑上的编辑器，不用敲 git 命令，手机、平板、别人的电脑都能写；部署平台（Vercel / Cloudflare Pages / EdgeOne 等）本来就会在收到提交后自动构建，所以「保存」约等于「发布」。</p><div><div><div></div><div>建议</div></div><div><p>如果你在本地已经有一套顺手的写作流程，Pages CMS 也不冲突：出门在外用 CMS 补一篇，回来 git pull 接着写就行，两边操作的是同一个仓库。</p></div></div><hr /></section>
<section><h2>二、接入三步走<a href="#二接入三步走"><span>#</span></a></h2><section><h3>2.1 仓库根目录新建 <code>.pages.yml</code><a href="#21-仓库根目录新建-pagesyml"><span>#</span></a></h3><p>这是 Pages CMS 的唯一配置文件，它读取内容集合、字段、媒体目录全靠它。一份能直接用的完整配置在<a href="#%E5%9B%9B%E5%AE%8C%E6%95%B4%E9%85%8D%E7%BD%AE%E5%8F%AF%E7%9B%B4%E6%8E%A5%E6%8A%84">第四节</a>，抄进去先跑通，再看第五节的逐段讲解按需调整。</p></section><section><h3>2.2 建好媒体上传目录<a href="#22-建好媒体上传目录"><span>#</span></a></h3><p>配置里会把上传图片的目录指到 <code>public/images</code>，空目录 Git 不会跟踪，先放一个占位文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>-p</span><span> </span><span>public/images</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>touch</span><span> </span><span>public/images/.gitkeep</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2.3 打开 CMS 确认<a href="#23-打开-cms-确认"><span>#</span></a></h3><p>登录 <a href="https://app.pagescms.org" target="_blank">app.pagescms.org</a>（用 GitHub 账号授权），选中你的仓库和分支，左侧就会出现配置里定义的内容集合和 Media 面板。侧边栏 Admin 里的 <strong>Configuration</strong> 页面显示的就是 <code>.pages.yml</code> 本身，以后想改配置也可以直接在这个输入框里改，保存即提交。</p><div><div><div></div><div>提示</div></div><div><p>Pages CMS 按分支读配置，确认页面地址里的分支和你部署用的分支一致（Firefly 默认是 <code>master</code>）。</p></div></div><hr /></section></section>
<section><h2>三、避坑前置：字段必须覆盖 front matter<a href="#三避坑前置字段必须覆盖-front-matter"><span>#</span></a></h2><p>这一节是整篇文章最重要的一句话，先说结论：</p><div><div><div></div><div>警告</div></div><div><p><strong>CMS 编辑器里没有的字段，保存时会被直接从 front matter 里删掉。</strong></p></div></div><p>Pages CMS 保存一篇文章时，是按 <code>.pages.yml</code> 里定义的字段列表重新生成 front matter 的。我在接入过程中就实际中过招：旧配置里没有定义 <code>author</code> 字段，用 CMS 保存一次文章后，front matter 里的 <code>author: 世琰</code> 就没了。</p><p>所以动手之前，先翻一下自己文章的 front matter，把所有想保留的键都在配置里给出对应字段——包括 <code>author</code>、<code>password</code>、<code>series</code> 这类低频字段。Firefly 主题自带的字段全集见 <code>src/content.config.ts</code>，照着抄不会漏。</p><hr /></section>
<section><h2>四、完整配置（可直接抄）<a href="#四完整配置可直接抄"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># Pages CMS 配置，字段与 src/content.config.ts 的 Astro 内容集合一一对应；</span></div></div><div><div><div>2</div></div><div><span># 留空的字段保存时会被自动省略，交给 Astro 的 zod 默认值处理，不会写出 null / ""。</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>media</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>input</span><span>: </span><span>public/images</span><span>        </span><span># 上传的文件存到仓库的这个目录</span></div></div><div><div><div>6</div></div><div><span>  </span><span>output</span><span>: </span><span>/images</span><span>             </span><span># 写进文章里的 URL 前缀</span></div></div><div><div><div>7</div></div><div><span>  </span><span>rename</span><span>: </span><span>random</span><span>              </span><span># 上传时随机重命名，避免中文文件名和重名覆盖</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>content</span><span>:</span></div></div><div><div><div>10</div></div><div><span>  </span><span># ── 文章 ─────────────────────────────────────────────</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>- </span><span>name</span><span>: </span><span>posts</span></div></div><div><div><div>12</div></div><div><span>    </span><span>label</span><span>: </span><span>文章</span></div></div><div><div><div>13</div></div><div><span>    </span><span>type</span><span>: </span><span>collection</span></div></div><div><div><div>14</div></div><div><span>    </span><span>path</span><span>: </span><span>src/content/posts</span></div></div><div><div><div>15</div></div><div><span>    </span><span>format</span><span>: </span><span>yaml-frontmatter</span></div></div><div><div><div>16</div></div><div><span>    </span><span>filename</span><span>:</span></div></div><div><div><div>17</div></div><div><span>      </span><span>template</span><span>: </span><span>'{year}-{month}-{day}-{primary}.md'</span></div></div><div><div><div>18</div></div><div><span>      </span><span>field</span><span>: </span><span>create</span><span>           </span><span># 新建文章时弹出文件名输入框，自己起名</span></div></div><div><div><div>19</div></div><div><span>    </span><span>view</span><span>:</span></div></div><div><div><div>20</div></div><div><span>      </span><span>fields</span><span>: [</span><span>title</span><span>, </span><span>published</span><span>, </span><span>draft</span><span>, </span><span>category</span><span>]</span></div></div><div><div><div>21</div></div><div><span>      </span><span>primary</span><span>: </span><span>title</span></div></div><div><div><div>22</div></div><div><span>      </span><span>sort</span><span>: [</span><span>published</span><span>]</span></div></div><div><div><div>23</div></div><div><span>      </span><span>default</span><span>:</span></div></div><div><div><div>24</div></div><div><span>        </span><span>sort</span><span>: </span><span>published</span></div></div><div><div><div>25</div></div><div><span>        </span><span>order</span><span>: </span><span>desc</span></div></div><div><div><div>26</div></div><div><span>    </span><span># 集合会把目录里的图片也当成条目列出来，把已有配图的文件名列进来即可</span></div></div><div><div><div>27</div></div><div><span>    </span><span>exclude</span><span>: [</span><span>cover.png</span><span>, </span><span>hero.png</span><span>, </span><span>my-cover.png</span><span>]</span></div></div><div><div><div>28</div></div><div><span>    </span><span>fields</span><span>:</span></div></div><div><div><div>29</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>title</span></div></div><div><div><div>30</div></div><div><span>        </span><span>label</span><span>: </span><span>标题</span></div></div><div><div><div>31</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>32</div></div><div><span>        </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>33</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>published</span></div></div><div><div><div>34</div></div><div><span>        </span><span>label</span><span>: </span><span>发布日期</span></div></div><div><div><div>35</div></div><div><span>        </span><span>type</span><span>: </span><span>date</span></div></div><div><div><div>36</div></div><div><span>        </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>37</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>updated</span></div></div><div><div><div>38</div></div><div><span>        </span><span>label</span><span>: </span><span>更新日期</span></div></div><div><div><div>39</div></div><div><span>        </span><span>type</span><span>: </span><span>date</span></div></div><div><div><div>40</div></div><div><span>        </span><span>default</span><span>: </span><span>''</span><span>           </span><span># 不自动填今天，需要时再手动选</span></div></div><div><div><div>41</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>draft</span></div></div><div><div><div>42</div></div><div><span>        </span><span>label</span><span>: </span><span>草稿</span></div></div><div><div><div>43</div></div><div><span>        </span><span>type</span><span>: </span><span>boolean</span></div></div><div><div><div>44</div></div><div><span>        </span><span>default</span><span>: </span><span>false</span></div></div><div><div><div>45</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>description</span></div></div><div><div><div>46</div></div><div><span>        </span><span>label</span><span>: </span><span>摘要</span></div></div><div><div><div>47</div></div><div><span>        </span><span>type</span><span>: </span><span>text</span></div></div><div><div><div>48</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>image</span></div></div><div><div><div>49</div></div><div><span>        </span><span>label</span><span>: </span><span>封面图</span></div></div><div><div><div>50</div></div><div><span>        </span><span>type</span><span>: </span><span>image</span></div></div><div><div><div>51</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>category</span></div></div><div><div><div>52</div></div><div><span>        </span><span>label</span><span>: </span><span>分类</span></div></div><div><div><div>53</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>54</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>tags</span></div></div><div><div><div>55</div></div><div><span>        </span><span>label</span><span>: </span><span>标签</span></div></div><div><div><div>56</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>57</div></div><div><span>        </span><span>list</span><span>: </span><span>true</span></div></div><div><div><div>58</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>pinned</span></div></div><div><div><div>59</div></div><div><span>        </span><span>label</span><span>: </span><span>置顶</span></div></div><div><div><div>60</div></div><div><span>        </span><span>type</span><span>: </span><span>boolean</span></div></div><div><div><div>61</div></div><div><span>        </span><span>default</span><span>: </span><span>false</span></div></div><div><div><div>62</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>lang</span></div></div><div><div><div>63</div></div><div><span>        </span><span>label</span><span>: </span><span>语言</span></div></div><div><div><div>64</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>65</div></div><div><span>        </span><span>description</span><span>: </span><span>留空跟随站点默认</span></div></div><div><div><div>66</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>author</span></div></div><div><div><div>67</div></div><div><span>        </span><span>label</span><span>: </span><span>作者</span></div></div><div><div><div>68</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>69</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>comment</span></div></div><div><div><div>70</div></div><div><span>        </span><span>label</span><span>: </span><span>开启评论</span></div></div><div><div><div>71</div></div><div><span>        </span><span>type</span><span>: </span><span>boolean</span></div></div><div><div><div>72</div></div><div><span>        </span><span>default</span><span>: </span><span>true</span></div></div><div><div><div>73</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>password</span></div></div><div><div><div>74</div></div><div><span>        </span><span>label</span><span>: </span><span>访问密码</span></div></div><div><div><div>75</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>76</div></div><div><span>        </span><span>description</span><span>: </span><span>留空则不加密</span></div></div><div><div><div>77</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>passwordHint</span></div></div><div><div><div>78</div></div><div><span>        </span><span>label</span><span>: </span><span>密码提示</span></div></div><div><div><div>79</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>80</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>series</span></div></div><div><div><div>81</div></div><div><span>        </span><span>label</span><span>: </span><span>系列名</span></div></div><div><div><div>82</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>83</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>seriesOrder</span></div></div><div><div><div>84</div></div><div><span>        </span><span>label</span><span>: </span><span>系列序号</span></div></div><div><div><div>85</div></div><div><span>        </span><span>type</span><span>: </span><span>number</span></div></div><div><div><div>86</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>sourceLink</span></div></div><div><div><div>87</div></div><div><span>        </span><span>label</span><span>: </span><span>原文链接</span></div></div><div><div><div>88</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>89</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>licenseName</span></div></div><div><div><div>90</div></div><div><span>        </span><span>label</span><span>: </span><span>许可证名称</span></div></div><div><div><div>91</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>92</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>licenseUrl</span></div></div><div><div><div>93</div></div><div><span>        </span><span>label</span><span>: </span><span>许可证链接</span></div></div><div><div><div>94</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>95</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>body</span></div></div><div><div><div>96</div></div><div><span>        </span><span>label</span><span>: </span><span>正文</span></div></div><div><div><div>97</div></div><div><span>        </span><span>type</span><span>: </span><span>rich-text</span></div></div><div><div><div>98</div></div><div>
</div></div><div><div><div>99</div></div><div><span>  </span><span># ── 动态（说说 / 微博式短内容）────────────────────────</span></div></div><div><div><div>100</div></div><div><span><span>  </span></span><span>- </span><span>name</span><span>: </span><span>dynamic</span></div></div><div><div><div>101</div></div><div><span>    </span><span>label</span><span>: </span><span>动态</span></div></div><div><div><div>102</div></div><div><span>    </span><span>type</span><span>: </span><span>collection</span></div></div><div><div><div>103</div></div><div><span>    </span><span>path</span><span>: </span><span>src/content/dynamic</span></div></div><div><div><div>104</div></div><div><span>    </span><span>format</span><span>: </span><span>yaml-frontmatter</span></div></div><div><div><div>105</div></div><div><span>    </span><span>filename</span><span>: </span><span>'{year}-{month}-{day}-{hour}{minute}{second}.md'</span></div></div><div><div><div>106</div></div><div><span>    </span><span>view</span><span>:</span></div></div><div><div><div>107</div></div><div><span>      </span><span>fields</span><span>: [</span><span>published</span><span>, </span><span>pinned</span><span>, </span><span>location</span><span>]</span></div></div><div><div><div>108</div></div><div><span>      </span><span>primary</span><span>: </span><span>published</span></div></div><div><div><div>109</div></div><div><span>      </span><span>sort</span><span>: [</span><span>published</span><span>]</span></div></div><div><div><div>110</div></div><div><span>      </span><span>default</span><span>:</span></div></div><div><div><div>111</div></div><div><span>        </span><span>sort</span><span>: </span><span>published</span></div></div><div><div><div>112</div></div><div><span>        </span><span>order</span><span>: </span><span>desc</span></div></div><div><div><div>113</div></div><div><span>    </span><span>fields</span><span>:</span></div></div><div><div><div>114</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>published</span></div></div><div><div><div>115</div></div><div><span>        </span><span>label</span><span>: </span><span>发布时间</span></div></div><div><div><div>116</div></div><div><span>        </span><span>type</span><span>: </span><span>date</span></div></div><div><div><div>117</div></div><div><span>        </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>118</div></div><div><span>        </span><span>options</span><span>:</span></div></div><div><div><div>119</div></div><div><span>          </span><span>time</span><span>: </span><span>true</span></div></div><div><div><div>120</div></div><div><span>          </span><span>format</span><span>: </span><span>'yyyy-MM-dd HH:mm:ss'</span></div></div><div><div><div>121</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>pinned</span></div></div><div><div><div>122</div></div><div><span>        </span><span>label</span><span>: </span><span>置顶</span></div></div><div><div><div>123</div></div><div><span>        </span><span>type</span><span>: </span><span>boolean</span></div></div><div><div><div>124</div></div><div><span>        </span><span>default</span><span>: </span><span>false</span></div></div><div><div><div>125</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>location</span></div></div><div><div><div>126</div></div><div><span>        </span><span>label</span><span>: </span><span>位置</span></div></div><div><div><div>127</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>128</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>body</span></div></div><div><div><div>129</div></div><div><span>        </span><span>label</span><span>: </span><span>内容</span></div></div><div><div><div>130</div></div><div><span>        </span><span>type</span><span>: </span><span>rich-text</span></div></div><div><div><div>131</div></div><div>
</div></div><div><div><div>132</div></div><div><span>  </span><span># ── 独立页面（about / friends / guestbook）────────────</span></div></div><div><div><div>133</div></div><div><span>  </span><span># 路由在主题里是写死的，禁用新建 / 重命名 / 删除</span></div></div><div><div><div>134</div></div><div><span><span>  </span></span><span>- </span><span>name</span><span>: </span><span>spec</span></div></div><div><div><div>135</div></div><div><span>    </span><span>label</span><span>: </span><span>独立页面</span></div></div><div><div><div>136</div></div><div><span>    </span><span>type</span><span>: </span><span>collection</span></div></div><div><div><div>137</div></div><div><span>    </span><span>path</span><span>: </span><span>src/content/spec</span></div></div><div><div><div>138</div></div><div><span>    </span><span>format</span><span>: </span><span>yaml-frontmatter</span></div></div><div><div><div>139</div></div><div><span>    </span><span>operations</span><span>:</span></div></div><div><div><div>140</div></div><div><span>      </span><span>create</span><span>: </span><span>false</span></div></div><div><div><div>141</div></div><div><span>      </span><span>rename</span><span>: </span><span>false</span></div></div><div><div><div>142</div></div><div><span>      </span><span>delete</span><span>: </span><span>false</span></div></div><div><div><div>143</div></div><div><span>    </span><span>view</span><span>:</span></div></div><div><div><div>144</div></div><div><span>      </span><span>fields</span><span>: [</span><span>title</span><span>, </span><span>description</span><span>]</span></div></div><div><div><div>145</div></div><div><span>      </span><span>primary</span><span>: </span><span>title</span></div></div><div><div><div>146</div></div><div><span>    </span><span>fields</span><span>:</span></div></div><div><div><div>147</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>title</span></div></div><div><div><div>148</div></div><div><span>        </span><span>label</span><span>: </span><span>页面标题</span></div></div><div><div><div>149</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>150</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>description</span></div></div><div><div><div>151</div></div><div><span>        </span><span>label</span><span>: </span><span>页面描述</span></div></div><div><div><div>152</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>153</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>body</span></div></div><div><div><div>154</div></div><div><span>        </span><span>label</span><span>: </span><span>页面内容</span></div></div><div><div><div>155</div></div><div><span>        </span><span>type</span><span>: </span><span>text</span><span>           </span><span># 纯文本源码模式，防止富文本弄乱 JSX</span></div></div><div><div><div>156</div></div><div>
</div></div><div><div><div>157</div></div><div><span>  </span><span># ── 项目展示（可选，不需要就整段删掉）────────────────</span></div></div><div><div><div>158</div></div><div><span><span>  </span></span><span>- </span><span>name</span><span>: </span><span>projects</span></div></div><div><div><div>159</div></div><div><span>    </span><span>label</span><span>: </span><span>项目</span></div></div><div><div><div>160</div></div><div><span>    </span><span>type</span><span>: </span><span>collection</span></div></div><div><div><div>161</div></div><div><span>    </span><span>path</span><span>: </span><span>src/content/projects</span></div></div><div><div><div>162</div></div><div><span>    </span><span>format</span><span>: </span><span>yaml-frontmatter</span></div></div><div><div><div>163</div></div><div><span>    </span><span>filename</span><span>:</span></div></div><div><div><div>164</div></div><div><span>      </span><span>template</span><span>: </span><span>'{primary}.md'</span></div></div><div><div><div>165</div></div><div><span>      </span><span>field</span><span>: </span><span>create</span></div></div><div><div><div>166</div></div><div><span>    </span><span>exclude</span><span>: [</span><span>images</span><span>]</span></div></div><div><div><div>167</div></div><div><span>    </span><span>view</span><span>:</span></div></div><div><div><div>168</div></div><div><span>      </span><span>fields</span><span>: [</span><span>title</span><span>, </span><span>published</span><span>, </span><span>status</span><span>, </span><span>order</span><span>]</span></div></div><div><div><div>169</div></div><div><span>      </span><span>primary</span><span>: </span><span>title</span></div></div><div><div><div>170</div></div><div><span>      </span><span>sort</span><span>: [</span><span>published</span><span>]</span></div></div><div><div><div>171</div></div><div><span>      </span><span>default</span><span>:</span></div></div><div><div><div>172</div></div><div><span>        </span><span>sort</span><span>: </span><span>published</span></div></div><div><div><div>173</div></div><div><span>        </span><span>order</span><span>: </span><span>desc</span></div></div><div><div><div>174</div></div><div><span>    </span><span>fields</span><span>:</span></div></div><div><div><div>175</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>title</span></div></div><div><div><div>176</div></div><div><span>        </span><span>label</span><span>: </span><span>项目名</span></div></div><div><div><div>177</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>178</div></div><div><span>        </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>179</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>published</span></div></div><div><div><div>180</div></div><div><span>        </span><span>label</span><span>: </span><span>发布日期</span></div></div><div><div><div>181</div></div><div><span>        </span><span>type</span><span>: </span><span>date</span></div></div><div><div><div>182</div></div><div><span>        </span><span>required</span><span>: </span><span>true</span></div></div><div><div><div>183</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>draft</span></div></div><div><div><div>184</div></div><div><span>        </span><span>label</span><span>: </span><span>草稿</span></div></div><div><div><div>185</div></div><div><span>        </span><span>type</span><span>: </span><span>boolean</span></div></div><div><div><div>186</div></div><div><span>        </span><span>default</span><span>: </span><span>false</span></div></div><div><div><div>187</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>order</span></div></div><div><div><div>188</div></div><div><span>        </span><span>label</span><span>: </span><span>排序权重</span></div></div><div><div><div>189</div></div><div><span>        </span><span>type</span><span>: </span><span>number</span></div></div><div><div><div>190</div></div><div><span>        </span><span>description</span><span>: </span><span>越大越靠前</span></div></div><div><div><div>191</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>description</span></div></div><div><div><div>192</div></div><div><span>        </span><span>label</span><span>: </span><span>描述</span></div></div><div><div><div>193</div></div><div><span>        </span><span>type</span><span>: </span><span>text</span></div></div><div><div><div>194</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>image</span></div></div><div><div><div>195</div></div><div><span>        </span><span>label</span><span>: </span><span>封面图</span></div></div><div><div><div>196</div></div><div><span>        </span><span>type</span><span>: </span><span>image</span></div></div><div><div><div>197</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>status</span></div></div><div><div><div>198</div></div><div><span>        </span><span>label</span><span>: </span><span>状态</span></div></div><div><div><div>199</div></div><div><span>        </span><span>type</span><span>: </span><span>select</span></div></div><div><div><div>200</div></div><div><span>        </span><span>options</span><span>:</span></div></div><div><div><div>201</div></div><div><span>          </span><span>values</span><span>: [</span><span>planning</span><span>, </span><span>developing</span><span>, </span><span>published</span><span>, </span><span>archived</span><span>]</span></div></div><div><div><div>202</div></div><div><span>          </span><span>placeholder</span><span>: </span><span>未设置</span></div></div><div><div><div>203</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>tags</span></div></div><div><div><div>204</div></div><div><span>        </span><span>label</span><span>: </span><span>标签</span></div></div><div><div><div>205</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>206</div></div><div><span>        </span><span>list</span><span>: </span><span>true</span></div></div><div><div><div>207</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>lang</span></div></div><div><div><div>208</div></div><div><span>        </span><span>label</span><span>: </span><span>语言</span></div></div><div><div><div>209</div></div><div><span>        </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>210</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>link</span></div></div><div><div><div>211</div></div><div><span>        </span><span>label</span><span>: </span><span>相关链接</span></div></div><div><div><div>212</div></div><div><span>        </span><span>type</span><span>: </span><span>object</span></div></div><div><div><div>213</div></div><div><span>        </span><span>list</span><span>: </span><span>true</span></div></div><div><div><div>214</div></div><div><span>        </span><span>fields</span><span>:</span></div></div><div><div><div>215</div></div><div><span><span>          </span></span><span>- </span><span>name</span><span>: </span><span>label</span></div></div><div><div><div>216</div></div><div><span>            </span><span>label</span><span>: </span><span>名称</span></div></div><div><div><div>217</div></div><div><span>            </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>218</div></div><div><span><span>          </span></span><span>- </span><span>name</span><span>: </span><span>icon</span></div></div><div><div><div>219</div></div><div><span>            </span><span>label</span><span>: </span><span>图标</span></div></div><div><div><div>220</div></div><div><span>            </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>221</div></div><div><span>            </span><span>description</span><span>: </span><span>Iconify 图标名，如 fa7-brands:github</span></div></div><div><div><div>222</div></div><div><span><span>          </span></span><span>- </span><span>name</span><span>: </span><span>value</span></div></div><div><div><div>223</div></div><div><span>            </span><span>label</span><span>: </span><span>链接地址</span></div></div><div><div><div>224</div></div><div><span>            </span><span>type</span><span>: </span><span>string</span></div></div><div><div><div>225</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>body</span></div></div><div><div><div>226</div></div><div><span>        </span><span>label</span><span>: </span><span>详情</span></div></div><div><div><div>227</div></div><div><span>        </span><span>type</span><span>: </span><span>rich-text</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><div><div></div><div>提示</div></div><div><p>我的仓库里 <code>exclude</code> 实际排除了 30 个历史配图文件名，上面为了可读性缩成了示例。把你自己 <code>src/content/posts</code> 目录下的图片文件名列全就行。</p></div></div><hr /></section>
<section><h2>五、配置逐段拆解<a href="#五配置逐段拆解"><span>#</span></a></h2><section><h3>5.1 media：图片传到哪、URL 长什么样<a href="#51-media图片传到哪url-长什么样"><span>#</span></a></h3>

<table><thead><tr><th>键</th><th>含义</th></tr></thead><tbody><tr><td><code>input</code></td><td>上传的文件在仓库里的实际存放路径</td></tr><tr><td><code>output</code></td><td>写进文章 front matter / 正文里的 URL 前缀</td></tr><tr><td><code>rename</code></td><td>上传重命名策略：<code>random</code> 随机名 / <code>safe</code> 拼接原名的安全字符 / <code>false</code> 保留原名</td></tr></tbody></table><p>Firefly 的封面和正文图片同时支持相对路径、<code>/</code> 开头的 public 路径和远程 URL，所以 <code>input: public/images</code> + <code>output: /images</code> 是最省事的组合：所有图片统一进 public，写进文章的就是 <code>/images/xxx.png</code>，本地和线上都能直接访问。</p></section><section><h3>5.2 filename：新文件的命名规则<a href="#52-filename新文件的命名规则"><span>#</span></a></h3><ul>
<li><code>{year}</code> <code>{month}</code> <code>{day}</code> <code>{hour}</code> <code>{minute}</code> <code>{second}</code>：创建时间，零填充；</li>
<li><code>{primary}</code>：列表视图的主字段（一般是标题），会做 slug 化处理；</li>
<li><code>field: create</code>：新建时弹出文件名输入框。</li>
</ul><p>文章的文件名（去掉扩展名）就是 URL 的一部分，所以发布后尽量别再改名。动态这类短内容用纯时间戳命名，完全不用操心。</p></section><section><h3>5.3 view：列表怎么排<a href="#53-view列表怎么排"><span>#</span></a></h3><p><code>fields</code> 是列表里显示的列，<code>primary</code> 是主标题列，<code>default.sort</code> + <code>order</code> 控制默认排序。文章按发布日期倒序，符合直觉。</p></section><section><h3>5.4 exclude：把图片从条目列表里藏起来<a href="#54-exclude把图片从条目列表里藏起来"><span>#</span></a></h3><p>Pages CMS 的集合会<strong>递归</strong>列出目录下的所有文件——包括 png。不处理的话，文章列表里会混进一堆「空条目」。<code>exclude</code> 按<strong>精确文件名</strong>匹配（不支持通配符），把已有配图的文件名列进去就好；新图片一律走 Media 上传到 <code>public/images</code>，别再往内容目录里放。</p></section><section><h3>5.5 operations：给危险操作上锁<a href="#55-operations给危险操作上锁"><span>#</span></a></h3><p><code>create</code> / <code>rename</code> / <code>delete</code> 三个开关控制集合的新建、重命名、删除。独立页面的路由在主题里是写死的（<code>about.astro</code>、<code>friends.astro</code>、<code>guestbook.astro</code> 分别读固定文件），误删一个页面就是 404，所以全部禁用。</p></section><section><h3>5.6 date 字段的 options<a href="#56-date-字段的-options"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>- </span><span>name</span><span>: </span><span>published</span></div></div><div><div><div>2</div></div><div><span>  </span><span>type</span><span>: </span><span>date</span></div></div><div><div><div>3</div></div><div><span>  </span><span>options</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>time</span><span>: </span><span>true</span><span>                        </span><span># 带时间选择器</span></div></div><div><div><div>5</div></div><div><span>    </span><span>format</span><span>: </span><span>'yyyy-MM-dd HH:mm:ss'</span><span>     </span><span># 写入文件的格式（date-fns 语法）</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>default: ''</code> 可以阻止日期字段自动填当前时间——<code>updated</code> 用得上，需要时再手动选。</p><hr /></section></section>
<section><h2>六、踩坑记录<a href="#六踩坑记录"><span>#</span></a></h2><section><h3>6.1 日期必须保持「裸格式」，否则构建直接炸<a href="#61-日期必须保持裸格式否则构建直接炸"><span>#</span></a></h3><p>Firefly 的内容 schema 里 <code>published</code> 是 <code>z.date()</code>，Astro 解析 front matter 时只把<strong>不带引号</strong>的日期识别成 Date：<code>published: 2026-09-12</code> 没问题，<code>published: "2026-09-12"</code> 会变成字符串，构建报错。</p><p>Pages CMS 的 date 字段默认写裸日期，正常用不会出事。风险点在于<strong>绕过 CMS 手改文件</strong>：在 GitHub 网页上编辑时手一抖给日期加了对引号，下次部署就炸。这是静态博客 + 任何 CMS 组合的共性坑，遇到构建失败先查日期。</p></section><section><h3>6.2 空字段不会写出 null，但必填字段别空着<a href="#62-空字段不会写出-null但必填字段别空着"><span>#</span></a></h3><p>CMS 保存时会自动省略值为空的键，正好符合 Astro schema 的默认值语义，<code>image</code>、<code>updated</code> 这类可选字段留空即可。但 <code>title</code>、<code>published</code> 这种 schema 里没有默认值的字段一定要填——配置里给它们标了 <code>required: true</code>，保存时 CMS 会先拦一道。</p></section><section><h3>6.3 纯中文标题生成不了文件名<a href="#63-纯中文标题生成不了文件名"><span>#</span></a></h3><p><code>{primary}</code> 生成文件名时会做 slug 化，纯中文标题 slug 化后是<strong>空字符串</strong>。所以文章的 filename 模板用日期打头、并在新建时弹出输入框让你自己起名（中英文都行，Firefly 对中文文件名的 URL 处理是正常的）。</p></section><section><h3>6.4 MDX 文件别用富文本编辑器<a href="#64-mdx-文件别用富文本编辑器"><span>#</span></a></h3><p><code>friends.mdx</code> 这类 MDX 文件里可能有 JSX 注释和组件语法，富文本编辑器（Tiptap）往返一遍很可能弄乱结构。所以配置里独立页面的正文用 <code>type: text</code>——一个纯文本框，所见即所得地编辑原始 Markdown，保存时一个字节都不会动。</p></section><section><h3>6.5 复杂 Markdown 建议切到 source 模式<a href="#65-复杂-markdown-建议切到-source-模式"><span>#</span></a></h3><p>文章正文的富文本编辑器右上角可以切换 <strong>editor / source</strong> 两种模式。日常写段落、插图用 editor 方便；写 callout（<code>&gt; [!NOTE]</code>）、复杂表格、大量代码块时切到 source 模式，保存的是原始 Markdown，最稳。</p></section><section><h3>6.6 「via Pages CMS」的提交会规范化文件格式<a href="#66-via-pages-cms的提交会规范化文件格式"><span>#</span></a></h3><p>用 CMS 保存过的文件会被重新序列化：front matter 的行内数组会变成块列表、表格对齐会被重排、多余的空行会被整理。这些都是合法的等价改写，git diff 大不代表内容变了。但如果两个配置的字段列表不一致，就会有第三节的「字段被删」问题——这就是为什么字段要配全。</p><hr /></section></section>
<section><h2>七、日常使用<a href="#七日常使用"><span>#</span></a></h2><section><h3>7.1 写一篇新文章<a href="#71-写一篇新文章"><span>#</span></a></h3><ol>
<li>侧边栏 → 文章 → 右上角新建；</li>
<li>弹出文件名输入框，填一个 <code>2026-09-12-my-post</code> 风格的名字；</li>
<li>填标题（必填）、发布日期默认今天，标签是一行一个；</li>
<li>正文用富文本编辑器写，需要图片就点工具栏的插图按钮直接上传，图会传到 <code>public/images</code> 并插入 <code>/images/xxx.png</code>；</li>
<li>保存。此时 GitHub 上多一个 commit，部署平台开始构建，一两分钟后线上可见。</li>
</ol></section><section><h3>7.2 草稿流程<a href="#72-草稿流程"><span>#</span></a></h3><p>没写完的文章把「草稿」勾上（<code>draft: true</code>），主题不会把它渲染进列表和详情页，可以放心保存占位。写完取消勾选再保存即发布。</p></section><section><h3>7.3 改独立页面<a href="#73-改独立页面"><span>#</span></a></h3><p>关于页、友链、留言板都在「独立页面」里，正文是原始 Markdown 源码模式，改完直接保存。新增独立页面需要先在主题里加路由，光建 md 文件没用，所以配置里禁掉了新建。</p><div><div><div></div><div>建议</div></div><div><p>本地改完东西想用 CMS 写作之前，记得先 <code>git pull</code>，反之 CMS 上写完回来也要先 pull——两边是同一个仓库，别让自己处在冲突状态。</p></div></div><hr /></section></section>
<section><h2>八、故障排查<a href="#八故障排查"><span>#</span></a></h2>

<table><thead><tr><th>症状</th><th>可能原因</th><th>处理</th></tr></thead><tbody><tr><td>保存了但网站没更新</td><td>部署平台还在构建，或构建失败</td><td>去部署平台看构建日志</td></tr><tr><td>构建失败，报 <code>z.date()</code> / Invalid date</td><td>日期被写成了带引号的字符串</td><td>检查该文 front matter 的日期字段</td></tr><tr><td>构建失败，报某字段类型错误</td><td>手改文件时 front matter 格式错了</td><td>对照 <code>src/content.config.ts</code> 检查</td></tr><tr><td>Media 面板打不开 / 上传失败</td><td><code>public/images</code> 目录不存在</td><td>建目录并提交 <code>.gitkeep</code></td></tr><tr><td>文章列表里出现图片「条目」</td><td><code>exclude</code> 没覆盖到新图片</td><td>补 exclude，或把图移去 <code>public/images</code></td></tr><tr><td>改了 <code>.pages.yml</code> 没生效</td><td>看错了分支，或页面是旧缓存</td><td>确认分支一致后刷新页面</td></tr></tbody></table><div><div><div></div><div>注意</div></div><div><p>构建失败时部署平台上的还是上一个成功版本，网站不会白屏，但你的修改也不会生效。修好 front matter 再推一次即可。</p></div></div><hr /></section>
<section><h2>九、结语<a href="#九结语"><span>#</span></a></h2><p>整套方案的本质是把「能改仓库文件的 GitHub 账号」变成一个带表单校验和媒体库的写作后台：配置成本只有一份 <code>.pages.yml</code>，收益是随时随地可写、格式有 CMS 兜底、提交历史照样干净。</p><p>如果你也在用 Firefly，欢迎直接抄第四节的配置；踩到别的坑欢迎来<a href="/guestbook/">留言板</a>交流。</p><hr /><blockquote><p><strong>文档版本：</strong> 2026-09-12
<strong>适用主题：</strong> Firefly（Astro 内容集合架构）
<strong>适用平台：</strong> app.pagescms.org / Vercel / Cloudflare Pages / EdgeOne Pages 等自动部署平台</p></blockquote></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/zlion-home-vue3/</id>
      <title type="text">个人主页重写：Vue 3 毛玻璃 Bento + 洛雪音源</title>
      <published>2026-09-12T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>世琰</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/zlion-home-vue3/"/>
      <summary type="text">用 Vue 3 + Vite 重写个人导航主页：唯一运行时依赖是 vue，构建产物 gzip 约 60KB。毛玻璃 Bento 布局 + 全配置驱动的「探索更多」面板（新闻/热榜/音乐/Epic 限免/历史上的今天/站点监控）；音乐播放器接上洛雪音源，自定义音源脚本跑在 serverless 函数里。零环境变量部署到 Vercel，fork 改一个 config.js 就能变成你自己的主页</summary>
      <content type="html"><![CDATA[<blockquote><p><strong>在线访问：</strong> <a href="https://www.zlion.top" target="_blank">www.zlion.top</a> ｜ <strong>开源仓库：</strong> <a href="https://github.com/Zlion-Y/homepage" target="_blank">Zlion-Y/homepage</a>
布局思路最初参考了 <a href="https://github.com/imsyy/home" target="_blank">imsyy/home</a>（MIT License，已存档），在此致谢——除此之外没有搬它的代码，依赖也全部换掉了。</p></blockquote>
<p></p><figure><img alt="主页效果：壁纸背景 + 毛玻璃卡片" loading="lazy" width="1440" height="801" src="/_astro/preview.Cn3XdYvG_Z48Bu6.webp" /><figcaption>主页效果：壁纸背景 + 毛玻璃卡片</figcaption></figure><p></p>
<section><h2>为什么自己写一份<a href="#为什么自己写一份"><span>#</span></a></h2><p>想要的东西其实很简单：<strong>站点导航 + 一言 + 时钟 + 天气 + 音乐</strong>。imsyy/home 的布局我很喜欢，但它已经归档不再维护，而且 Element Plus、Pinia、APlayer、Swiper 一整套对一个导航页来说太重了。于是用 Vue 3 + Vite 重写了一份：<strong>唯一运行时依赖就是 vue 本身</strong>，构建产物 gzip 约 60KB（JS 52KB + CSS 9KB）。</p></section>
<section><h2>功能一览<a href="#功能一览"><span>#</span></a></h2><ul>
<li><strong>毛玻璃 Bento 布局</strong>：一言、时钟、天气长卡、网站列表小卡自由拼装，移动端自动降级单列</li>
<li><strong>一言</strong>：Hitokoto API，失败自动兜底本地语句，点卡片换一句</li>
<li><strong>实时时钟</strong>：农历 + 节日倒数</li>
<li><strong>实时天气长卡</strong>：uapis 聚合接口，<strong>免 Key</strong>、按访客 IP 自动定位到县级（IPv6 正常），湿度/体感/AQI、未来几天高低温双曲线，可固定城市</li>
<li><strong>博客更新卡</strong>：自动拉取博客 RSS 展示最新 3 篇文章，卡头带 GitHub / 邮箱等社交图标</li>
<li><strong>网站列表</strong>：一行 3 个紧凑卡片，<code>config.js</code> 里加站点自动扩展</li>
<li><strong>二级「探索更多」面板</strong>：点左上角 Logo 进入，桌面一屏六卡——
<ul>
<li>每日新闻（60s API）</li>
<li>多平台热榜（微博/B站/V2EX/IT之家等，平台与顺序可配置）</li>
<li>音乐播放器（下一节重点）</li>
<li>Epic 限免</li>
<li>程序员历史上的今天</li>
<li>站点监控（访客浏览器直连探测自己各站点，绿红点 + 响应耗时，60 秒自动刷新，无需第三方监控服务）</li>
</ul>
</li>
<li><strong>动效</strong>：卡片悬停 3D 倾斜 + 光泽、自定义圆点光标 + 点击涟漪、卡片错峰浮起</li>
<li><strong>细节</strong>：极光渐变背景随昼夜变色、支持自定义壁纸/随机壁纸 API、页脚建站天数、站名 7 款手写字体一键切换</li>
</ul><p>所有卡片都能在 <code>config.js</code> 里开关与排序。</p></section>
<section><h2>音乐播放器：多源自愈 + 洛雪音源<a href="#音乐播放器多源自愈--洛雪音源"><span>#</span></a></h2><section><h3>播放器本身<a href="#播放器本身"><span>#</span></a></h3><ul>
<li>多 Meting 源<strong>并发竞速</strong>拉歌单，谁先返回用谁，单源故障无感</li>
<li>每首歌自动在多个源之间探测可用播放链接，断链/过期自愈，全败自动跳下一首</li>
<li>歌单本地缓存 6 小时，进面板秒开；预载但不自动出声，点了播放才响</li>
<li>歌词同步滚动居中，点歌词行跳转进度</li>
</ul></section><section><h3>为什么要接洛雪音源<a href="#为什么要接洛雪音源"><span>#</span></a></h3><p>公共 Meting 接口有个绕不过去的边界：<strong>VIP / 版权受限曲目拿不到完整直链</strong>——它给的是一段能正常播的 30 秒试听片段，不报错、很难察觉。于是把本地的洛雪音源解析搬到 serverless 上，让「点播放」这件事有一个更可靠的后端。</p></section><section><h3>请求链路<a href="#请求链路"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>浏览器                       Vercel 边缘</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>│ ① 拉歌单（多源并发竞速）</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>├──────────────────────────────► 公共 Meting × N</span></div></div><div><div><div>5</div></div><div><span><span>   </span></span><span>│ ◄── 谁先返回用谁</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>7</div></div><div><span><span>   </span></span><span>│ ② 取直链（同源，无 CORS）</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>├──────────────────────────────► /api/url</span></div></div><div><div><div>9</div></div><div><span><span>   </span></span><span>│                                  │</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>│                                  ├─ 命中 CDN 缓存？直接返回（不进函数）</span></div></div><div><div><div>11</div></div><div><span><span>   </span></span><span>│                                  │</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>│                                  └─ 未命中 → 函数内并发多音源解析</span></div></div><div><div><div>13</div></div><div><span><span>   </span></span><span>│                                                  │</span></div></div><div><div><div>14</div></div><div><span><span>   </span></span><span>│                                                  ├─ 按成绩分波对冲</span></div></div><div><div><div>15</div></div><div><span><span>   </span></span><span>│                                                  ├─ 赢家一出即掐断其余</span></div></div><div><div><div>16</div></div><div><span><span>   </span></span><span>│                                                  ├─ 直链探活</span></div></div><div><div><div>17</div></div><div><span><span>   </span></span><span>│                                                  └─ 连续失败熔断</span></div></div><div><div><div>18</div></div><div><span><span>   </span></span><span>│ ◄── 完整直链 ────────────────────────────────────┘</span></div></div><div><div><div>19</div></div><div><span><span>   </span></span><span>│</span></div></div><div><div><div>20</div></div><div><span><span>   </span></span><span>│ ③ 解析不到 → 自动降级回 Meting（开着也不影响原本能用的）</span></div></div><div><div><div>21</div></div><div><span><span>   </span></span><span>▼</span></div></div><div><div><div>22</div></div><div><span>&lt;audio&gt; 播放</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>落到代码里，几个关键设计：</p><ul>
<li><strong>自定义音源脚本跑在 Vercel 函数里</strong>（<code>api/</code> + <code>lib/</code>，跟主页一起部署），多音源按成绩分波对冲、赢家一出即掐断其余、直链探活、连续失败熔断</li>
<li>前端调<strong>同源</strong>的 <code>/api/url</code>：不需要额外域名、证书、CORS 配置，也不用再养一台服务器</li>
<li>直链缓存在 CDN 边缘（15 分钟），<strong>命中缓存的请求根本不进函数</strong>，不消耗调用次数</li>
<li>解析不到时自动降级回 Meting，开着也不影响原来能用的情况</li>
</ul><p><code>config.js</code> 里两个开关：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>musicSource</span><span>: </span></span><span>"proxy"</span><span>,    </span><span>// "meting" = 只走公共 Meting；"proxy" = 走自带的 serverless 解析</span></div></div><div><div><div>2</div></div><div><span><span>musicQuality</span><span>: </span></span><span>"320k"</span><span>,    </span><span>// 128k / 320k / flac / flac24bit</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>音源脚本放 <a href="https://github.com/Zlion-Y/homepage/tree/main/sources" target="_blank"><code>sources/</code></a> 一起部署；也可以配 <code>SOURCE_URLS</code> 环境变量指向在线脚本——改完远端脚本请求一次 <code>/api/health?refresh=1</code>，函数立刻重装全部音源，<strong>连重新部署都不用</strong>。部署后打开 <code>/api/health</code> 能看到装上了哪些音源、各自的平台与失败原因。</p><blockquote><p>提醒：函数默认跑在香港（<code>hkg1</code>，离国内接口最近），实测与国内出口的成功率一致；但这类「直链解析」本身在灰区，建议自用、别公开分发。</p></blockquote></section></section>
<section><h2>部署<a href="#部署"><span>#</span></a></h2><section><h3>平台托管（推荐）<a href="#平台托管推荐"><span>#</span></a></h3><p>Vercel / Netlify / Cloudflare Pages 均可，Framework 选 <code>Vite</code>，根目录 <code>./</code>、输出目录 <code>dist</code>、构建命令 <code>npm run build</code>，<strong>零环境变量</strong>（免 Key 接口的好处）。</p><p><a href="https://vercel.com/new/clone?repository-url=https://github.com/Zlion-Y/homepage&amp;project-name=zlion-homepage&amp;repository-name=zlion-homepage" target="_blank"></a></p><figure><a href="https://vercel.com/new/clone?repository-url=https://github.com/Zlion-Y/homepage&amp;project-name=zlion-homepage&amp;repository-name=zlion-homepage" target="_blank"><img src="https://vercel.com/button" alt="Deploy with Vercel" /><figcaption>Deploy with Vercel</figcaption></a></figure><p></p></section><section><h3>本地开发<a href="#本地开发"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/you-github-name/homepage.git</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>homepage</span></div></div><div><div><div>3</div></div><div><span>npm</span><span> </span><span>install</span></div></div><div><div><div>4</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>dev</span><span>     </span><span># http://localhost:5173，配置热更新</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>全部配置化：只改 <code>src/config.js</code><a href="#全部配置化只改-srcconfigjs"><span>#</span></a></h2><p>个性化集中在一个文件，改完 push 即自动重新部署。常用的几项：</p>

<table><thead><tr><th>配置项</th><th>说明</th></tr></thead><tbody><tr><td><code>siteName</code> / <code>pageTitle</code> / <code>greet</code> / <code>desc</code> / <code>motto</code></td><td>站名、标签页标题、问候语、一句话介绍、打字机轮换标语</td></tr><tr><td><code>siteFont</code></td><td>站名手写体，内置 7 款开源字体一键切换</td></tr><tr><td><code>logo</code></td><td>留空用内置「Z」徽章，填图片路径/URL 整体替换</td></tr><tr><td><code>siteStart</code> / <code>author</code> / <code>repo</code></td><td>建站日期（页脚运行天数）、署名、署名链接</td></tr><tr><td><code>homeCards</code></td><td>主页各卡片开关（greet / blog / hitokoto / clock / weather / siteLinks）</td></tr><tr><td><code>panelCards</code></td><td>二级面板卡片排列：数组顺序 = 排列顺序，删掉某项 = 隐藏该卡</td></tr><tr><td><code>musicPlaylist</code> / <code>musicSource</code> / <code>musicQuality</code></td><td>歌单 ID、直链来源、音质</td></tr><tr><td><code>weatherCity</code></td><td>天气城市 adcode，留空 = 按访客 IP 自动定位</td></tr><tr><td><code>bgApi</code></td><td>背景：留空极光渐变 / 放 <code>public/images/background.jpg</code> / 填随机壁纸 API</td></tr><tr><td><code>hotPlatforms</code></td><td>热榜平台与顺序</td></tr><tr><td><code>siteMonitors</code></td><td>站点监控卡的目标站点列表</td></tr><tr><td><code>socialLinks</code> / <code>siteLinks</code></td><td>社交图标、网站列表</td></tr></tbody></table><p>换成你自己的站点，改 <code>siteLinks</code> 和 <code>socialLinks</code> 就行。</p></section>
<section><h2>TODO<a href="#todo"><span>#</span></a></h2><ul>
<li> 更多卡片支持（欢迎 PR / Issue 讨论）</li>
<li> 卡片位置自定义（拖拽排序 / 布局记忆）</li>
<li> 音乐卡 AMLL 歌词动效（逐字点亮、弹性动画、灵感封面背景）</li>
</ul></section>
<section><h2>致谢<a href="#致谢"><span>#</span></a></h2><ul>
<li><a href="https://github.com/imsyy/home" target="_blank">imsyy/home</a>——布局灵感来源；</li>
<li><a href="https://github.com/CuteLeaf/Firefly" target="_blank">CuteLeaf/Firefly</a>——音乐播放器多源自愈设计参考；</li>
<li><a href="https://uapis.cn/" target="_blank">uapis.cn</a>、<a href="https://github.com/vikiboss/60s" target="_blank">60s API</a>、<a href="https://hitokoto.cn/" target="_blank">hitokoto.cn</a>——免 Key 数据接口；</li>
<li><a href="https://lucide.dev/" target="_blank">Lucide</a>——图标。</li>
</ul><p>项目为 vibe coding 实现，若侵犯了您的权益请联系我删除。觉得有用的话，去 <a href="https://github.com/Zlion-Y/homepage" target="_blank">仓库</a> 点个 Star 就是对我最大的支持～</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/fileserver-single-binary/</id>
      <title type="text">fileserver：单文件自建文件服务器（GO）</title>
      <published>2026-09-09T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>世琰</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/fileserver-single-binary/"/>
      <summary type="text">用一个 Go 编译的二进制文件在服务器 / NAS / 路由器上自建文件服务：分片断点续传上传、限时限次分享链接、WebDAV、媒体相册预览、一键自更新，零运行时依赖。另有一个介绍页站点，先把功能和部署方式讲清楚再让你下载。</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档面向想在服务器 / NAS / 路由器上自建文件分享服务的用户，涵盖功能介绍、三种部署方式、安全设计与介绍页站点。
<strong>适用版本：</strong> fileserver v1.3.5
<strong>适用系统：</strong> Linux（amd64 / arm64 / armv7 / armv5 / mipsle / 386）/ Windows / macOS
<strong>适用平台：</strong> 云服务器 / 本地虚拟机 / NAS / 软路由 / Docker</p></blockquote>
<hr />
<section><h2>一、为什么要再造一个文件服务器<a href="#一为什么要再造一个文件服务器"><span>#</span></a></h2><p>网盘限速、第三方工具要装 Docker 拉一堆镜像、老 NAS 上连 Node 都跑不动——fileserver 就是为了解决这些场景：<strong>一个可执行文件，拷过去就能跑</strong>。</p><p>它的定位很简单：</p><ul>
<li><strong>单个二进制文件</strong>：Go 编写、零第三方库，前端页面经 <code>go:embed</code> 直接编译进二进制，无任何运行时依赖；图片缩略图、文本预览、统计图全部在浏览器端渲染，弱机服务器只负责透传字节流</li>
<li><strong>8 个平台开箱即用</strong>：从 x86 云服务器到 mipsle 架构的老路由器，总有一个包能跑</li>
<li><strong>核心功能齐全</strong>：目录管理、分片断点续传上传、限时限次分享链接、WebDAV 直连、回收站、一键自更新</li>
</ul><a href="https://github.com/Zlion-Y/fileserver" target="_blank"><div><div><div><div></div><div>Zlion-Y</div></div><div>/</div><div>fileserver</div></div><div></div></div><div>Description not set</div><div><div>—</div><div>—</div><div>no-license</div><span>Go</span></div></a><section><h3>配套介绍页<a href="#配套介绍页"><span>#</span></a></h3><p>仓库之外还有一个独立的介绍页站点，把「这东西是干什么的、能干什么、怎么装」在一页里讲完，比翻 README 快得多：</p><blockquote><p><strong>介绍页：</strong> <a href="https://storage.zlion.top" target="_blank">storage.zlion.top</a></p></blockquote><p>页面结构大致是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>介绍页 (fileserver.zlion.top)</span></div></div><div><div><div>2</div></div><div><span>├─ 首屏       一句话定位 + 一键部署命令 + Release 下载入口</span></div></div><div><div><div>3</div></div><div><span>├─ 功能总览   分片上传 / 分享链接 / WebDAV / 相册视图 …逐项配截图</span></div></div><div><div><div>4</div></div><div><span>├─ 部署方式   直接运行 / 一键脚本 / Docker 三条路径并列对比</span></div></div><div><div><div>5</div></div><div><span>├─ 安全设计   PBKDF2、AES-GCM、路径越权防护等实现说明</span></div></div><div><div><div>6</div></div><div><span>└─ 常见问题   反代 127.0.0.1、413、别名冲突 …就地给排查方向</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>定位上它是「软件的门面」，承担三件事：<strong>讲清楚是什么</strong>（省得用户读完 README 还在猜）、<strong>给出最快的上手路径</strong>（首屏就放 curl 一键部署）、<strong>沉淀文档</strong>（安全设计、故障排查这些正文不适合塞进 README 的内容）。介绍页与本文内容同源，改动软件时两边一起更新。</p><hr /></section></section>
<section><h2>二、功能总览<a href="#二功能总览"><span>#</span></a></h2>

<table><thead><tr><th>功能</th><th>说明</th></tr></thead><tbody><tr><td>文件管理</td><td>上传、删除（进回收站）、重命名 / 移动、新建文件夹、勾选批量操作</td></tr><tr><td>分片上传</td><td>≥8MB 自动分片断点续传：失败只补缺失分片，服务重启自动重建会话，对网页端无感</td></tr><tr><td>文件夹上传</td><td>「上传文件夹」递归上传整个目录结构；把文件夹直接拖进页面同样整体上传</td></tr><tr><td>相册视图</td><td>图片文件夹一键切换缩略图网格，浏览器端懒加载，点击直接预览</td></tr><tr><td>媒体 / 文本预览</td><td>图片 / 视频 / 音频浏览器内直接播放（支持拖动）；txt / md / 源码 / 配置直接查看</td></tr><tr><td>搜索排序</td><td>全盘递归文件名搜索、按名称 / 大小 / 时间排序、按类型与大小筛选，选择自动记忆</td></tr><tr><td>大目录优化</td><td>分页拉取 + 前端虚拟滚动，万级文件目录也只渲染可见行</td></tr><tr><td>分享链接</td><td>有效期（1 小时 ~ 30 天 / 永久 / 自定义）+ 下载次数（1/5/20/不限）双限制</td></tr><tr><td>自定义别名</td><td>分享链接可设自定义尾段（如 <code>/s/发布会-materials</code>），对外发布更好看，原 token 链接兼容</td></tr><tr><td>防盗链</td><td>按分享开启 Referer 校验，拒绝其他网站的页面引用，直访与下载工具不受影响</td></tr><tr><td>分享密码</td><td>PBKDF2 哈希校验，密码 AES-GCM 加密存储，管理端可查看</td></tr><tr><td>直链 / 确认页</td><td>直链打开即下载（媒体文件内联展示）；确认页模式先展示文件信息与预览再计数</td></tr><tr><td>分享管理</td><td>筛选、批量撤销、行内快速编辑有效期 / 密码 / 别名 / 访问方式，一键复制、扫码访问</td></tr><tr><td>下载统计</td><td>分享管理页展示最近 30 天下载 / 失败趋势图（canvas 客户端绘制）</td></tr><tr><td>访问日志</td><td>每条分享记录最近 50 次访问（IP / UA / 成败原因），行内展开查看</td></tr><tr><td>回收站</td><td>删除先进回收站，可逐条或勾选批量恢复 / 彻底删除，超期自动清理</td></tr><tr><td>WebDAV</td><td><code>/dav/</code> 挂载文件根目录，Salt Player 等播放器与移动端文件管理器可直连</td></tr><tr><td>一键自更新</td><td>自动下载对应架构二进制 → 校验 → 原子替换 → 自动重启，也可本地上传更新包</td></tr><tr><td>安全与会话</td><td>查看活跃会话、远程踢出、网页改密立即生效并踢出其他会话</td></tr><tr><td>深色模式 / PWA</td><td>深浅主题跟随系统或手动切换；支持「添加到主屏幕」独立窗口运行</td></tr><tr><td>移动端适配</td><td>卡片式列表、贴底批量栏、底部抽屉弹窗，按触控目标重新设计</td></tr><tr><td>防爆破</td><td>同一 IP 5 分钟内认证失败 10 次自动封禁 15 分钟</td></tr><tr><td>定时清理</td><td>过期分享记录自动清理，保留时长可视化配置</td></tr></tbody></table><hr /></section>
<section><h2>三、大文件分片上传<a href="#三大文件分片上传"><span>#</span></a></h2><p>家用宽带上传几个 GB 的文件，最怕的就是传到 90% 断网重来。大文件（≥8MB）自动走分片通道：前端把文件切成 4MB 的分片逐个上传，服务端顺序追加落盘，完成后同盘原子改名——<strong>中途断网只补缺失的分片，已经传上去的字节不会浪费</strong>。</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>是</p></span></div><div><span></span></div><div><span><p>否</p></span></div><div><span><p>选择 ≥8MB 大文件</p></span></div><div><span><p>init：服务端建立分片会话</p></span></div><div><span><p>前端按 4MB 逐片上传追加</p></span></div><div><span><p>是否中断</p></span></div><div><span><p>按服务端实际进度校正偏移<br /><br />只重传缺失分片</p></span></div><div><span><p>complete：校验长度后原子改名生效</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>是</p></span></div><div><span></span></div><div><span><p>否</p></span></div><div><span><p>选择 ≥8MB 大文件</p></span></div><div><span><p>init：服务端建立分片会话</p></span></div><div><span><p>前端按 4MB 逐片上传追加</p></span></div><div><span><p>是否中断</p></span></div><div><span><p>按服务端实际进度校正偏移<br /><br />只重传缺失分片</p></span></div><div><span><p>complete：校验长度后原子改名生效</p></span></div>
</div></div></div><p>会话对客户端完全透明：普通小文件仍走一次性请求，分片文件在完成前对文件列表、搜索、WebDAV、ZIP 打包全部不可见，不会外泄半成品。</p><hr /></section>
<section><h2>四、分享与浏览的几处细节<a href="#四分享与浏览的几处细节"><span>#</span></a></h2><section><h3>4.1 相册视图<a href="#41-相册视图"><span>#</span></a></h3><p>管理图片目录时点「相册」，列表切换为缩略图网格——服务端依旧只返回原图字节，由浏览器懒加载，不增加服务器负担。翻图点开即看，不用先下载到本地。</p></section><section><h3>4.2 分享别名<a href="#42-分享别名"><span>#</span></a></h3><p>创建分享时可自定义链接尾段，把默认的随机 token 换成可读的短词：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>默认：  https://files.example.com/s/a7f3c9e21b8d4f06</span></div></div><div><div><div>2</div></div><div><span>别名：  https://files.example.com/s/发布会-materials</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>发到网站 / 公众号里体面得多。别名全局唯一，原 token 链接保持兼容；别名段同样计入有效期与下载次数限制，不额外开权限口子。</p></section><section><h3>4.3 防盗链<a href="#43-防盗链"><span>#</span></a></h3><p>对公开外发的链接可单独<strong>按分享</strong>开启 Referer 校验：其他网站的页面引用直接返回 403，而直接在浏览器打开链接、用 IDM / aria2 这类下载工具拉取都不受影响——防的是被别站白嫖流量，不是防真实访客。</p><hr /></section></section>
<section><h2>五、快速开始<a href="#五快速开始"><span>#</span></a></h2><section><h3>5.1 直接运行<a href="#51-直接运行"><span>#</span></a></h3><p>从 <a href="https://github.com/Zlion-Y/fileserver/releases" target="_blank">Releases</a> 下载对应架构的二进制，赋予执行权限后运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>chmod</span><span> </span><span>+x</span><span> </span><span>fileserver</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>./fileserver</span><span> </span><span>--port</span><span> </span><span>16666</span><span> </span><span>--dir</span><span> </span><span>/data/files</span><span> </span><span>--user</span><span> </span><span>admin</span><span> </span><span>--pass</span><span> </span><span>你的密码</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>打开 <code>http://服务器IP:16666</code> 即可使用。不设 <code>--user/--pass</code> 时无认证，仅建议纯内网使用。</p></section><section><h3>5.2 常用启动参数<a href="#52-常用启动参数"><span>#</span></a></h3>

<table><thead><tr><th>参数</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>--port</code></td><td><code>8080</code></td><td>监听端口</td></tr><tr><td><code>--dir</code></td><td>无</td><td>文件根目录（也可启动后在网页里选）</td></tr><tr><td><code>--data-dir</code></td><td><code>./data</code></td><td>配置与分享记录存放目录，<strong>必须持久化</strong></td></tr><tr><td><code>--user</code> / <code>--pass</code></td><td>无</td><td>管理端账号密码</td></tr><tr><td><code>--max-upload-mb</code></td><td><code>0</code></td><td>单文件上传上限（MB），0 不限制，对分片上传同样生效</td></tr><tr><td><code>--trust-proxy</code></td><td><code>0</code></td><td>反向代理场景置 1</td></tr></tbody></table><p>同名环境变量（<code>PORT</code>、<code>ROOT_DIR</code>、<code>AUTH_PASS</code> 等）同样生效，命令行优先。</p><div><div><div></div><div>建议</div></div><div><p><code>--data-dir</code> 务必放在持久目录，分享记录存在里面的 <code>shares.json</code>，目录丢了链接全部失效；加密分享密码的主密钥 <code>.masterkey</code> 也在这里，迁移服务器时一并带走。</p></div></div><hr /></section></section>
<section><h2>六、三种部署方式怎么选<a href="#六三种部署方式怎么选"><span>#</span></a></h2><p>介绍页里把三条路径并列放在一起，选哪条看你的环境：</p><div><div><div><div><span><p>有 systemd 的 Linux</p></span></div><div><span><p>已跑 Docker</p></span></div><div><span><p>临时试用 / 特殊平台</p></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>你的环境</p></span></div><div><span><p>一键脚本<br /><br />识别架构 · 注册自启</p></span></div><div><span><p>Docker Compose<br /><br />数据卷持久化</p></span></div><div><span><p>直接运行<br /><br />下载二进制手起</p></span></div><div><span><p>网页访问 :16666</p></span></div>
</div><div><div><span><p>有 systemd 的 Linux</p></span></div><div><span><p>已跑 Docker</p></span></div><div><span><p>临时试用 / 特殊平台</p></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>你的环境</p></span></div><div><span><p>一键脚本<br /><br />识别架构 · 注册自启</p></span></div><div><span><p>Docker Compose<br /><br />数据卷持久化</p></span></div><div><span><p>直接运行<br /><br />下载二进制手起</p></span></div><div><span><p>网页访问 :16666</p></span></div>
</div></div></div><section><h3>6.1 一键部署到 Linux（推荐）<a href="#61-一键部署到-linux推荐"><span>#</span></a></h3><p>部署脚本自动识别 CPU 架构、从 Release 下载二进制、放行防火墙、注册 systemd 开机自启：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-fsSL</span><span> </span><span>https://raw.githubusercontent.com/Zlion-Y/fileserver/main/install-remote.sh</span><span> </span><span>-o</span><span> </span><span>install-remote.sh</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>sudo</span><span> </span><span>bash</span><span> </span><span>install-remote.sh</span><span> </span><span>--dry-run</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>sudo</span><span> </span><span>FS_USER=admin</span><span> </span><span>FS_PASS=你的密码</span><span> </span><span>bash</span><span> </span><span>install-remote.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>提示</div></div><div><p><code>--dry-run</code> 只预览将要执行的操作，不改系统，建议先跑一次确认。</p></div></div><p>装好后的常用运维操作：</p>

<table><thead><tr><th>需求</th><th>命令</th></tr></thead><tbody><tr><td>换目录 / 端口</td><td><code>sudo DIR=/mnt/disk/share PORT=9000 bash install-remote.sh</code></td></tr><tr><td>指定版本安装</td><td><code>sudo VERSION=v1.3.5 bash install-remote.sh</code></td></tr><tr><td>升级到最新版</td><td>重跑安装即可，自动继承已有配置与密码</td></tr><tr><td>查看日志</td><td><code>journalctl -u fileserver -f</code></td></tr><tr><td>卸载</td><td><code>sudo bash install-remote.sh --uninstall</code></td></tr></tbody></table><p>配置文件在 <code>/etc/fileserver.env</code>，改完 <code>systemctl restart fileserver</code> 生效。</p></section><section><h3>6.2 Docker 部署<a href="#62-docker-部署"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cp</span><span> </span><span>.env.example</span><span> </span><span>.env</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>docker-compose.yml</code> 默认只监听 <code>127.0.0.1:16666</code>，由 Nginx / Caddy 反代后对外。数据落在 <code>./files</code> 与 <code>./data</code>，容器重建不丢。</p><div><div><div></div><div>警告</div></div><div><p>Docker 场景升级请重新构建镜像，不要在容器内替换二进制；未注入 <code>--build-arg VERSION</code> 时版本为 <code>dev</code>，网页会禁用一键更新。</p></div></div><hr /></section></section>
<section><h2>七、一键自更新是怎么工作的<a href="#七一键自更新是怎么工作的"><span>#</span></a></h2><p>个人服务器最烦的就是升级：登服务器、找架构、下载、替换、重启。fileserver 把这整套做成了一个按钮：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>通过</p></span></div><div><span><p>失败</p></span></div><div><span></span></div><div><span></span></div><div><span><p>网页点击一键更新</p></span></div><div><span><p>服务端查询 GitHub 最新 Release</p></span></div><div><span><p>按当前架构下载对应二进制</p></span></div><div><span><p>校验二进制魔数</p></span></div><div><span><p>原子替换旧文件</p></span></div><div><span><p>中止并报错</p></span></div><div><span><p>自动重启新版本</p></span></div><div><span><p>网页展示版本更新日志</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>通过</p></span></div><div><span><p>失败</p></span></div><div><span></span></div><div><span></span></div><div><span><p>网页点击一键更新</p></span></div><div><span><p>服务端查询 GitHub 最新 Release</p></span></div><div><span><p>按当前架构下载对应二进制</p></span></div><div><span><p>校验二进制魔数</p></span></div><div><span><p>原子替换旧文件</p></span></div><div><span><p>中止并报错</p></span></div><div><span><p>自动重启新版本</p></span></div><div><span><p>网页展示版本更新日志</p></span></div>
</div></div></div><p>服务器连不上 GitHub 也没关系：本地下载好二进制，从网页直接上传更新包（上限 256MB），校验通过后同样替换重启。</p><hr /></section>
<section><h2>八、WebDAV：播放器直连<a href="#八webdav播放器直连"><span>#</span></a></h2><p>服务地址即 <code>http://&lt;主机&gt;:&lt;端口&gt;/dav/</code>，认证同管理端账号密码。Salt Player、foobar2000、常规移动端文件管理器都能直连，音乐 / 视频流式播放支持拖动进度条；通过 WebDAV 删除的文件同样进回收站，防误删。</p><div><div><div></div><div>说明</div></div><div><p>未实现 LOCK/UNLOCK（Class 2），Windows 资源管理器「映射网络驱动器」不受支持；常规播放器与文件管理器不受影响。</p></div></div><hr /></section>
<section><h2>九、安全设计<a href="#九安全设计"><span>#</span></a></h2><ul>
<li><strong>口令哈希</strong>：管理端与分享密码均用 PBKDF2-HMAC-SHA256（管理端 10 万轮 / 分享端 5 万轮），旧版单次 SHA-256 记录校验通过后自动就地升级，无需用户操作</li>
<li><strong>密码加密存储</strong>：分享密码以 AES-GCM 加密落盘，主密钥独立文件存放；<code>config.json</code>（管理密码哈希）、<code>shares.json</code>（加密密码与访问日志）、主密钥文件均以 0600 权限落盘，泄露不再连带交出密码</li>
<li><strong>改密踢会话</strong>：修改管理员密码后立即踢出其他所有登录会话，被盗会话不会永久有效</li>
<li><strong>续传授权</strong>：下载断点续传必须持有真实下载后签发的 ticket Cookie，伪造 <code>Range: bytes=1-</code> 无法绕过一次性链接的次数限制</li>
<li><strong>路径越权</strong>：所有路径参数做根目录校验 + 符号链接兜底，<code>../</code> 逃逸与 root 内软链指向外部均无效</li>
<li><strong>防盗链</strong>：按分享开启 Referer 校验，外站页面引用返回 403，直访与下载工具不受影响</li>
<li><strong>上传限制</strong>：<code>MAX_UPLOAD_MB</code> 对普通与分片上传同样生效；分片临时文件对列表 / 搜索 / WebDAV / ZIP 不可见，半成品不会外泄</li>
</ul><hr /></section>
<section><h2>十、注意事项与故障排查<a href="#十注意事项与故障排查"><span>#</span></a></h2><div><div><div></div><div>重要</div></div><div><p>公网部署务必设置 <code>AUTH_USER</code> / <code>AUTH_PASS</code>，否则任何人都能管理你的文件。</p></div></div><div><div><div></div><div>反代注意</div></div><div><p>反向代理场景需设置 <code>TRUST_PROXY=1</code>，否则生成的分享链接会是 <code>http://127.0.0.1:...</code>；Nginx 需放开 <code>client_max_body_size 0</code> 并关闭 <code>proxy_buffering</code>。</p></div></div><p>常见问题排查：</p>

<table><thead><tr><th>现象</th><th>排查方向</th></tr></thead><tbody><tr><td>分享链接打开 404</td><td><code>shares.json</code> 是否随 <code>--data-dir</code> 丢失；文件是否被移动或删除</td></tr><tr><td>链接提示下载次数用完</td><td>限次已耗尽，可在分享管理里快速编辑重新放开</td></tr><tr><td>链接打开提示「禁止引用」</td><td>该分享开启了防盗链，外站引用被拒；从原页面打开或直访即可</td></tr><tr><td>别名提示已被占用</td><td>别名全局唯一，换一个或留空使用随机 token</td></tr><tr><td>反代后链接是 127.0.0.1</td><td>未设 <code>TRUST_PROXY=1</code>，或反代来源不在环回 / 内网信任范围</td></tr><tr><td>上传大文件失败 413</td><td>触发了 <code>MAX_UPLOAD_MB</code> 限制，或 Nginx 未放开 <code>client_max_body_size</code></td></tr><tr><td>大文件上传中断</td><td>直接重传即可，分片续传只补缺失部分，无需从头再来</td></tr><tr><td>一键更新报错</td><td>服务器连不上 GitHub，改用网页上传更新包方式</td></tr><tr><td>忘记管理密码</td><td>在配置中改 <code>AUTH_PASS</code> 后重启；网页改过密码的话删除 <code>config.json</code> 中的 <code>authPassHash</code> 字段即可回退到启动参数密码</td></tr></tbody></table><hr /><blockquote><p><strong>在线体验：</strong> <a href="https://fileserver.zlion.top:446" target="_blank">fileserver.zlion.top&lt;446&gt;</a> ｜ <strong>介绍页：</strong> <a href="https://fileserver.zlion.top" target="_blank">fileserver.zlion.top</a> ｜ <strong>开源仓库：</strong> <a href="https://github.com/Zlion-Y/fileserver" target="_blank">Zlion-Y/fileserver</a>
<strong>文档版本：</strong> 2026-09-22
<strong>软件版本：</strong> fileserver v1.3.5
<strong>适用平台：</strong> 云服务器 / 本地虚拟机 / NAS / 软路由 / Docker</p></blockquote></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/zte-e2631-mainline-linux-port/</id>
      <title type="text">中兴巡天AX3000 (E2631) 主线 Linux 内核移植全记录</title>
      <published>2026-08-31T00:00:00.000Z</published>
      <updated>2026-08-31T00:00:00.000Z</updated>
      <author><name>Shiyan</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/zte-e2631-mainline-linux-port/"/>
      <summary type="text">从固件解密、SoC 逆向到 6.18.38 主线内核上板验证的完整记录，覆盖 mihomo 全功能适配、Windows 交叉编译与开源复现流程</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档面向嵌入式开发与路由器逆向爱好者，完整记录中兴巡天 AX3000（E2631）从固件解密、SoC 逆向、交叉编译到主线内核上板验证的全过程，并给出 mihomo（Clash.Meta）全功能适配的配置与开源复现方式。
<strong>适用系统：</strong> Windows 10/11（MSYS2 构建） / WSL2（可选提速）
<strong>适用平台：</strong> 中兴 ZTE ZXHN E2631（巡天 AX3000，ZX279128S）</p></blockquote>
<hr />
<a href="https://github.com/Zlion-Y/e2631-mainline-zx279128s" target="_blank"><div><div><div><div></div><div>Zlion-Y</div></div><div>/</div><div>e2631-mainline-zx279128s</div></div><div></div></div><div>Description not set</div><div><div>—</div><div>—</div><div>NOASSERTION</div><span>C</span></div></a>
<hr />
<section><h2>一、项目背景与目标<a href="#一项目背景与目标"><span>#</span></a></h2><p>E2631 是中兴巡天 AX3000 千兆双频路由器，采用自研 ZX279128S SoC，运营商定制固件、源码不开放，内核特性老旧，既没有 TPROXY / nftables 等现代防火墙能力，也无法直接运行 mihomo（Clash.Meta）。网上流传的 openwrt-e2631 镜像只能通过 U-Boot 导入，实际只有镜像头能被识别为 OpenWrt，内核根本无法启动。</p><p>因此本项目决定走一条完整链路：<strong>固件解密 → SoC 逆向 → 串口/U-Boot 打通 → 主线内核编译 → 上板验证 → mihomo 适配 → 开源发布</strong>。</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>固件解密</p></span></div><div><span><p>SoC 逆向</p></span></div><div><span><p>串口 / U-Boot 打通</p></span></div><div><span><p>交叉编译主线 6.18.38</p></span></div><div><span><p>上板验证</p></span></div><div><span><p>mihomo 全功能适配</p></span></div><div><span><p>开源仓库发布</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>固件解密</p></span></div><div><span><p>SoC 逆向</p></span></div><div><span><p>串口 / U-Boot 打通</p></span></div><div><span><p>交叉编译主线 6.18.38</p></span></div><div><span><p>上板验证</p></span></div><div><span><p>mihomo 全功能适配</p></span></div><div><span><p>开源仓库发布</p></span></div>
</div></div></div><p>最终成果：主线内核 <strong>6.18.38</strong> 已在真实硬件上从 U-Boot 启动到交互式 busybox shell，TPROXY / TUN / nftables / zram 等特性全部验证通过，mihomo 可跑在 armv5 软浮点二进制下，且全部源码、工具、一键编译脚本已开源，可以直接拉取复现。</p><hr /></section>
<section><h2>二、硬件规格与 SoC 逆向<a href="#二硬件规格与-soc-逆向"><span>#</span></a></h2><section><h3>2.1 硬件规格<a href="#21-硬件规格"><span>#</span></a></h3>

<table><thead><tr><th>项目</th><th>规格</th></tr></thead><tbody><tr><td>SoC</td><td>中兴 ZX279128S（双核 Cortex-A9 rev1 @ 1GHz）</td></tr><tr><td>内存</td><td>256MB DDR3-1333（U-Boot 实测 256MiB；DTS 当前写 128MB 占位，可按需调整）</td></tr><tr><td>闪存</td><td>128MB SPI NAND（AMD/Spansion HYF1GQ4UTACAE）</td></tr><tr><td>有线</td><td>千兆 PHY（GEPHY ×4，驱动待移植）</td></tr><tr><td>无线</td><td>MT7916 WiFi（走 PCIe，驱动待移植）</td></tr><tr><td>USB</td><td>无对外 USB 口（dwc2 / dwc3 驱动已验证，DT 保持禁用）</td></tr><tr><td>调试串口</td><td>ZTE 定制 PL011 @ 0x94404000</td></tr></tbody></table><div><div><div></div><div>重要</div></div><div><p>这台机器<strong>没有 USB 口</strong>，后续所有调试、部署、镜像搬运都只能依赖 TFTP 和串口，这是整个调试方案设计的出发点。</p></div></div></section><section><h3>2.2 CPU 能力（最重要的事实）<a href="#22-cpu-能力最重要的事实"><span>#</span></a></h3><p>由 <code>cat /proc/cpuinfo</code> 实测，CPU features 为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>half thumb fastmult edsp thumbee tls</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>对照 ARMv7 完整特性集，结论如下：</p>

<table><thead><tr><th>特性</th><th>支持情况</th></tr></thead><tbody><tr><td>ARMv7（Cortex-A9）</td><td>✅ 支持</td></tr><tr><td>Thumb / ThumbEE</td><td>✅ 支持</td></tr><tr><td>EDSP（增强 DSP 指令）</td><td>✅ 支持</td></tr><tr><td>TLS（线程本地存储）</td><td>✅ 支持</td></tr><tr><td>VFP 浮点单元</td><td>❌ 不支持（无硬件 FPU）</td></tr><tr><td>NEON SIMD</td><td>❌ 不支持</td></tr><tr><td>硬件整数除法 idiv</td><td>❌ 不支持</td></tr></tbody></table><div><div><div></div><div>注意</div></div><div><p>无 VFP / NEON / idiv 意味着：</p><ul>
<li>所有二进制必须<strong>软浮点</strong>编译（<code>-mfloat-abi=soft</code>），用 <code>-mfpu=vfp</code> 或 hard-float 的二进制会直接非法指令崩溃；</li>
<li>除法、浮点运算全部走软件库，CPU 占用高，跑加密代理时会出现明显的 CPU 瓶颈；</li>
<li>内核编译时必须强制 <code>-marm</code>，避免工具链默认 Thumb 模式带来的启动问题。</li>
</ul></div></div></section><section><h3>2.3 串口 IP 逆向<a href="#23-串口-ip-逆向"><span>#</span></a></h3><p>E2631 的调试串口是 ZTE 定制的 PL011 兼容 IP，基地址 <code>0x94404000</code>，<code>earlycon=zteuart</code> 可输出引导日志，periphid 为 <code>0x001feffe</code>。它与标准 PL011 的寄存器偏移不同（与 ZX279133 是同一 IP）：</p>

<table><thead><tr><th>寄存器</th><th>标准 PL011 偏移</th><th>E2631（ZTE 定制）偏移</th></tr></thead><tbody><tr><td>DR（数据寄存器）</td><td>0x00</td><td>0x04</td></tr><tr><td>FR（标志寄存器）</td><td>0x18</td><td>0x14</td></tr><tr><td>IBRD</td><td>0x24</td><td>0x24</td></tr><tr><td>FBRD</td><td>0x28</td><td>0x28</td></tr><tr><td>LCRH</td><td>0x2C</td><td>0x30</td></tr><tr><td>CR</td><td>0x30</td><td>0x34</td></tr><tr><td>IFLS</td><td>0x34</td><td>0x38</td></tr><tr><td>IMSC</td><td>0x38</td><td>0x40</td></tr></tbody></table><div><div><div></div><div>提示</div></div><div><p>早期主线内核没有这个 ZTE 定制 earlycon 的适配，需要为 <code>zteuart</code> 编写 earlycon 驱动（已包含在项目的 patches 中）。串口物理参数为 3.3V TTL，115200-8-N-1。</p></div></div></section><section><h3>2.4 与 SR1010 的外设差异<a href="#24-与-sr1010-的外设差异"><span>#</span></a></h3><p>本项目基于 cnjn 的 SR1010 主线移植（linux-mainline-zte-zxslc-sr1010），但 <strong>E2631 的外围方案与 SR1010 不同</strong>，SoC 周边芯片不能直接照搬：</p>

<table><thead><tr><th>外设</th><th>SR1010</th><th>E2631</th></tr></thead><tbody><tr><td>有线 PHY</td><td>与 E2631 不同</td><td>GEPHY（千兆）</td></tr><tr><td>WiFi</td><td>与 E2631 不同</td><td>MT7916（PCIe）</td></tr><tr><td>SPI NAND</td><td>与 E2631 不同</td><td>HYF1GQ4UTACAE（128MB）</td></tr><tr><td>UART IP</td><td>同源</td><td>同源（ZTE 定制 PL011）</td></tr></tbody></table><p>所以设备树、网卡/PHY 驱动、闪存驱动都需要针对 E2631 单独适配，这也是”待办与未来工作”一章里列出的主要缺口。</p><hr /></section></section>
<section><h2>三、固件获取与解密<a href="#三固件获取与解密"><span>#</span></a></h2><section><h3>3.1 加密与密钥<a href="#31-加密与密钥"><span>#</span></a></h3><p>原厂固件的 kernel 分区使用 <strong>AES-128-ECB</strong> 加密，密钥常量为 <code>E263111559d0dfde</code>。解密后即得到标准 uImage（ih_load 为 <code>0x40008000</code>，这也是后面 XIP 陷阱的根源）。</p></section><section><h3>3.2 分区布局<a href="#32-分区布局"><span>#</span></a></h3><p>128MB SPI NAND 上的功能分区（具体偏移以固件 dump 为准）：</p>

<table><thead><tr><th>分区</th><th>内容</th><th>备注</th></tr></thead><tbody><tr><td>bootloader</td><td>U-Boot</td><td>串口可交互，命令未被完全阉割</td></tr><tr><td>kernel</td><td>加密内核</td><td>AES-128-ECB 解密后为 uImage</td></tr><tr><td>rootfs</td><td>只读根文件系统</td><td>JFFS2</td></tr><tr><td>kmodule</td><td>内核模块 + 用户态程序</td><td>JFFS2</td></tr><tr><td>配置区</td><td>出厂配置 / 无线参数</td><td>—</td></tr></tbody></table></section><section><h3>3.3 kmodule 解包与 vendor 运行库<a href="#33-kmodule-解包与-vendor-运行库"><span>#</span></a></h3><p>把 kmodule 分区按 JFFS2 解包后，拿到两样关键资产：</p><ul>
<li><strong>busybox v1.17.2</strong>（原厂用户态，后续 initramfs 直接复用）；</li>
<li><strong>uClibc 0.9.33.2 运行库</strong>（libc.so.0 / libm.so.0 / libresolv.so.0 等）。</li>
</ul><div><div><div></div><div>警告</div></div><div><p>vendor 的库文件在打包时被记录成了符号链接（直接拷出来是”不是 ELF 文件”），需要解析 <code>.symlink</code> 文件还原真实内容，才能用于构建 initramfs。</p></div></div><hr /></section></section>
<section><h2>四、串口与 U-Boot 引导<a href="#四串口与-u-boot-引导"><span>#</span></a></h2><section><h3>4.1 进入 U-Boot<a href="#41-进入-u-boot"><span>#</span></a></h3><p>上电后<strong>立即连续按 1</strong> 进入引导菜单，输入密码 <code>5cE080@fyBD</code>，回车后停在 U-Boot 命令行（自动启动已被停止）。原厂 U-Boot 支持 tftp / bootm / md / mw 等常用命令，可以直接用来引导主线内核。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 电脑端准备一个 TFTP 服务器（仓库里的 e2631-tools/tftpd2.py 即可）</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span># 串口里配置网络并下载内核</span></div></div><div><div><div>4</div></div><div><span>setenv</span><span> </span><span>serverip</span><span> </span><span>192.168.1.100</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>setenv</span><span> </span><span>ipaddr</span><span> </span><span>192.168.1.1</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>tftp</span><span> </span><span>0x43000000</span><span> </span><span>uImage-e2631.img</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>bootm</span><span> </span><span>0x43000000</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4.2 XIP 陷阱（本项目最大的坑）<a href="#42-xip-陷阱本项目最大的坑"><span>#</span></a></h3><div><div><div></div><div>注意</div></div><div><p><strong>绝对不要在 <code>0x40008000</code> 加载内核。</strong></p><p>uImage 头的 ih_load 就是 <code>0x40008000</code>。如果 tftp 直接下到这个地址，bootm 会认为镜像”已经就位”，走 XIP（就地执行）路径跳过搬运，64 字节的 uImage 头直接压在内核入口上，结果就是<strong>静默崩溃——串口没有任何输出</strong>。</p><p>另外 <code>0x42000000</code> 区域是 U-Boot 自用区，大文件传输会失败，也要避开。</p><p><strong>正确姿势：<code>tftp 0x43000000</code> → <code>bootm 0x43000000</code></strong>，bootm 会把镜像搬到 <code>0x40008000</code> 再跳转。</p></div></div></section><section><h3>4.3 完整引导链路<a href="#43-完整引导链路"><span>#</span></a></h3><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>上电</p></span></div><div><span><p>立即按 1 进菜单</p></span></div><div><span><p>输入密码 5cE080@fyBD</p></span></div><div><span><p>停在 U-Boot 命令行</p></span></div><div><span><p>tftp 0x43000000 下载 uImage</p></span></div><div><span><p>bootm 0x43000000</p></span></div><div><span><p>搬运镜像到 0x40008000</p></span></div><div><span><p>解压并跳转内核</p></span></div><div><span><p>earlycon=zteuart 输出启动日志</p></span></div><div><span><p>解包嵌入式 initramfs</p></span></div><div><span><p>init 挂载 console</p></span></div><div><span><p>spawn busybox sh</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>上电</p></span></div><div><span><p>立即按 1 进菜单</p></span></div><div><span><p>输入密码 5cE080@fyBD</p></span></div><div><span><p>停在 U-Boot 命令行</p></span></div><div><span><p>tftp 0x43000000 下载 uImage</p></span></div><div><span><p>bootm 0x43000000</p></span></div><div><span><p>搬运镜像到 0x40008000</p></span></div><div><span><p>解压并跳转内核</p></span></div><div><span><p>earlycon=zteuart 输出启动日志</p></span></div><div><span><p>解包嵌入式 initramfs</p></span></div><div><span><p>init 挂载 console</p></span></div><div><span><p>spawn busybox sh</p></span></div>
</div></div></div><hr /></section></section>
<section><h2>五、Windows 交叉编译环境<a href="#五windows-交叉编译环境"><span>#</span></a></h2><section><h3>5.1 环境组成<a href="#51-环境组成"><span>#</span></a></h3><p>项目在纯 Windows 上完成了整个内核构建，环境如下：</p>

<table><thead><tr><th>组件</th><th>版本/说明</th></tr></thead><tbody><tr><td>MSYS2</td><td>提供 make 4.x 与 POSIX 工具链</td></tr><tr><td>Arm GNU Toolchain</td><td>10.3（mingw 版），交叉编译内核</td></tr><tr><td>Zig</td><td>0.14（arm-linux-musleabi 静态），编译 initramfs 的 init 等用户态程序</td></tr><tr><td>Git</td><td>管理源码树与行尾</td></tr></tbody></table><div><div><div></div><div>建议</div></div><div><p>同一套源码在 WSL2（Ubuntu）里用完整 GCC 交叉编译，速度明显快于 MSYS2，cpio 打包也更省心。仓库同时提供 <code>build.sh</code>（Linux/WSL）和 <code>build.bat</code>（Windows/MSYS2），二选一即可。</p></div></div></section><section><h3>5.2 Windows 构建的强制限制<a href="#52-windows-构建的强制限制"><span>#</span></a></h3><div><div><div></div><div>重要</div></div><div><ul>
<li><code>make -j4</code> 是上限：MSYS2 下 <code>-j8</code> 会触发 make 段错误；</li>
<li>必须强制 <code>-marm</code>：mingw 工具链默认编 Thumb，会导致内核启动异常；</li>
<li>源码树必须保持 LF 行尾：clone 时 CRLF 污染了约 9 万文件，fixdep 解析依赖文件会出错，需要 CRLF 补丁。</li>
</ul></div></div><p>构建命令（对应 build.sh / build.bat 的核心）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>make</span><span> </span><span>ARCH=arm</span><span> </span><span>CROSS_COMPILE=arm-none-eabi-</span><span> </span><span>zx279128s_e2631_defconfig</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>make</span><span> </span><span>ARCH=arm</span><span> </span><span>CROSS_COMPILE=arm-none-eabi-</span><span> </span><span>-j4</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>5.3 踩过的编译坑（均已修复并固化在仓库）<a href="#53-踩过的编译坑均已修复并固化在仓库"><span>#</span></a></h3>

<table><thead><tr><th>问题</th><th>现象</th><th>处理</th></tr></thead><tbody><tr><td>CRLF 污染</td><td>fixdep 依赖文件解析错误</td><td>全树转 LF + 补丁</td></tr><tr><td>Thumb 默认编译</td><td>内核启动异常</td><td>强制 <code>-marm</code></td></tr><tr><td>make -j8 段错误</td><td>编译中途崩溃</td><td>限 <code>-j4</code></td></tr><tr><td>ntfs3 需要 gcc11 flag</td><td>编译报错</td><td>禁用 <code>CONFIG_NTFS3_FS</code></td></tr><tr><td>syncconfig 交互式提示</td><td>GCC_PLUGINS / DWARF 卡住构建</td><td>禁用 / 固定选项</td></tr><tr><td>initramfs_data.S 被误删</td><td>构建失败</td><td>git 恢复，重建脚本不再删除 .S</td></tr><tr><td>uapi netfilter 头文件是 stub</td><td>xt_mark.c 等编译失败</td><td>从 git 恢复真实内容并修复索引</td></tr><tr><td>busybox 用 zig 构建时 make 崩溃</td><td>段错误</td><td>改用 vendor busybox + uClibc 库</td></tr></tbody></table><hr /></section></section>
<section><h2>六、内核配置要点（mihomo 全功能适配）<a href="#六内核配置要点mihomo-全功能适配"><span>#</span></a></h2><section><h3>6.1 关键特性（全部 <code>=y</code> 并已上板验证）<a href="#61-关键特性全部-y-并已上板验证"><span>#</span></a></h3>

<table><thead><tr><th>内核特性</th><th>配置项</th><th>用途</th></tr></thead><tbody><tr><td>TUN 虚拟网卡</td><td><code>CONFIG_TUN</code></td><td>mihomo TUN 模式</td></tr><tr><td>TPROXY 透明代理</td><td><code>CONFIG_NETFILTER_XT_TARGET_TPROXY</code> + <code>CONFIG_NF_TPROXY_IPV4/IPV6</code></td><td>透明代理核心</td></tr><tr><td>socket 匹配</td><td><code>CONFIG_NETFILTER_XT_MATCH_SOCKET</code></td><td>按 socket 分流</td></tr><tr><td>nftables</td><td><code>CONFIG_NF_TABLES</code> + nft_tproxy / nft_socket / nft_compat</td><td>现代防火墙规则</td></tr><tr><td>iptables-legacy</td><td><code>CONFIG_IP_NF_IPTABLES_LEGACY</code> + <code>CONFIG_XTABLES_LEGACY</code></td><td>兼容原厂规则集</td></tr><tr><td>conntrack / NAT</td><td><code>CONFIG_NF_CONNTRACK</code> + MASQUERADE / REDIRECT / MARK</td><td>代理流量转发</td></tr><tr><td>策略路由</td><td><code>CONFIG_IP_ADVANCED_ROUTER</code> + <code>ip rule fwmark</code></td><td>按 fwmark 分流</td></tr><tr><td>IPv6</td><td><code>CONFIG_IPV6</code></td><td>双栈代理</td></tr><tr><td>zram</td><td><code>CONFIG_ZRAM</code></td><td>内存压缩，缓解 256MB 压力</td></tr></tbody></table></section><section><h3>6.2 netfilter 配置丢失的修复<a href="#62-netfilter-配置丢失的修复"><span>#</span></a></h3><p>内核默认配置做”平台精简”时会把 netfilter 相关选项丢掉，需要在 <code>olddefconfig</code> 之后显式重新打开：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>make</span><span> </span><span>ARCH=arm</span><span> </span><span>CROSS_COMPILE=arm-none-eabi-</span><span> </span><span>olddefconfig</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>scripts/config</span><span> </span><span>-e</span><span> </span><span>NETFILTER</span><span> </span><span>-e</span><span> </span><span>XTABLES_LEGACY</span><span> </span><span>-e</span><span> </span><span>IP_NF_IPTABLES_LEGACY</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>make</span><span> </span><span>ARCH=arm</span><span> </span><span>CROSS_COMPILE=arm-none-eabi-</span><span> </span><span>olddefconfig</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>6.3 嵌入式 initramfs<a href="#63-嵌入式-initramfs"><span>#</span></a></h3><div><div><div></div><div>提示</div></div><div><p>最初的方案是把 initrd 打成 uImage 传给内核，但 uImage 的 64 字节头会被内核误读，导致根文件系统无法挂载。最终改用 <code>CONFIG_INITRAMFS_SOURCE</code>，把 cpio 直接编进内核，干净可靠。</p></div></div><p>initramfs 内容（由 <code>e2631-tools/initramfs-src/</code> 构建）：</p><ul>
<li><code>init</code>：musl 静态编译，<code>O_RDWR</code> 打开 console、<code>TIOCSCTTY</code> 接管终端、spawn busybox sh；</li>
<li>busybox v1.17.2（vendor 版，经 uClibc 库支撑）；</li>
<li>uClibc 0.9.33.2 运行库（经 <code>.symlink</code> 修复后的真实 ELF）；</li>
<li>设备节点：console / null / tty / ttyAMA0(204&lt;64&gt;) / zero / random（<code>pack2.py</code> 递归 cpio 打包）。</li>
</ul><hr /></section></section>
<section><h2>七、上板验证<a href="#七上板验证"><span>#</span></a></h2><section><h3>7.1 启动结果<a href="#71-启动结果"><span>#</span></a></h3><p>主线内核 <strong>6.18.38</strong> 从 U-Boot 引导成功，<code>earlycon=zteuart</code> 输出完整启动日志，最终进入交互式 busybox shell。板上实测日志：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>=== E2631 mainline bring-up init reached userspace ===</span></div></div><div><div><div>2</div></div><div><span>[init] kernel booted, zteuart console OK</span></div></div><div><div><div>3</div></div><div><span>[init] Features : half thumb fastmult edsp thumbee tls</span></div></div><div><div><div>4</div></div><div><span>[init] handing console to busybox shell</span></div></div><div><div><div>5</div></div><div><span>/ # iptables -t mangle -L PREROUTING    # 正常</span></div></div><div><div><div>6</div></div><div><span>/ # ip rule add fwmark 1 lookup 100     # 正常</span></div></div><div><div><div>7</div></div><div><span>/ # busybox cat /proc/net/ip_tables_targets</span></div></div><div><div><div>8</div></div><div><span>...</span></div></div><div><div><div>9</div></div><div><span>TPROXY</span></div></div><div><div><div>10</div></div><div><span>TPROXY</span></div></div><div><div><div>11</div></div><div><span>MASQUERADE</span></div></div><div><div><div>12</div></div><div><span>REDIRECT</span></div></div><div><div><div>13</div></div><div><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>7.2 特性验证命令<a href="#72-特性验证命令"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 验证 TPROXY / REDIRECT / MASQUERADE / MARK 等目标已编进内核</span></div></div><div><div><div>2</div></div><div><span>cat</span><span> </span><span>/proc/net/ip_tables_targets</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 确认 CPU 特性（无 VFP / NEON / idiv）</span></div></div><div><div><div>5</div></div><div><span>cat</span><span> </span><span>/proc/cpuinfo</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 确认内存识别</span></div></div><div><div><div>8</div></div><div><span>cat</span><span> </span><span>/proc/meminfo</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>/proc/net/ip_tables_targets</code> 中能看到 <code>TPROXY</code> 等目标，说明透明代理内核路径完整可用；策略路由（<code>ip rule fwmark</code>）也已在板上实测通过。</p><hr /></section></section>
<section><h2>八、mihomo 适配与部署<a href="#八mihomo-适配与部署"><span>#</span></a></h2><section><h3>8.1 二进制选择<a href="#81-二进制选择"><span>#</span></a></h3><p>由于 CPU 无 VFP / NEON / idiv，mihomo 必须使用 <strong>armv5 软浮点</strong> 二进制：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mihomo-linux-armv5      # GOARM=5，软浮点</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>警告</div></div><div><p>不要用 armv7 或 hard-float 的 mihomo 包，否则在无 FPU 的 A9 上会直接非法指令崩溃。</p></div></div></section><section><h3>8.2 内核侧已就绪<a href="#82-内核侧已就绪"><span>#</span></a></h3><p>mihomo 需要的内核能力已全部 <code>=y</code> 编入（见第六章）：TUN、TPROXY（v4/v6）、xt_socket、nftables（nft_tproxy/socket/compat）、iptables-legacy、conntrack、NAT、策略路由、IPv6、zram。无需任何外部内核模块。</p></section><section><h3>8.3 部署与预期<a href="#83-部署与预期"><span>#</span></a></h3><p>部署方式：通过 tftp / 串口把 armv5 二进制与配置文件拷入，挂到 TUN 或 TPROXY 模式即可。注意这台机器没有 USB，所以文件传输只能走网络或串口。</p><div><div><div></div><div>CPU 瓶颈提示</div></div><div><p>无硬件 FPU / NEON，SS / SSR 节点的加解密全部由 CPU 软算，1GHz 双核 A9 + 256MB 内存跑满速代理时 CPU 会明显吃满（“CPU 爆缸”风险）。这是硬件上限；软件层面已尽量做到极致——特性齐全、软浮点、无非法指令崩溃。</p></div></div><hr /></section></section>
<section><h2>九、开源仓库与复现<a href="#九开源仓库与复现"><span>#</span></a></h2><section><h3>9.1 仓库结构<a href="#91-仓库结构"><span>#</span></a></h3><p>仓库 <a href="https://github.com/Zlion-Y/e2631-mainline-zx279128s" target="_blank">Zlion-Y/e2631-mainline-zx279128s</a> 本身就是<strong>完整的内核源码树</strong>（Linux 6.18.38 + E2631 平台适配全部落树），不再需要单独拉基础树再套补丁。默认分支 <code>main</code>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>e2631-mainline-zx279128s/                       # 完整内核源码树（GitHub）</span></div></div><div><div><div>2</div></div><div><span>├── arch/arm/boot/dts/zte/zx279128s-e2631.dts       # E2631 板级设备树</span></div></div><div><div><div>3</div></div><div><span>├── arch/arm/configs/zx279128s_e2631_defconfig      # 精简 defconfig（initramfs 已内嵌）</span></div></div><div><div><div>4</div></div><div><span>├── initramfs-e2631.cpio.gz                         # 嵌入式 initramfs（busybox + uClibc + init）</span></div></div><div><div><div>5</div></div><div><span>├── e2631-tools/</span></div></div><div><div><div>6</div></div><div><span>│   ├── tftpd2.py          # 简易 TFTP 服务器（UDP 69，服务父目录）</span></div></div><div><div><div>7</div></div><div><span>│   ├── pack_uimage.py     # uImage 打包（load/entry 0x40008000）</span></div></div><div><div><div>8</div></div><div><span>│   ├── serial_ctl.py      # 串口驱动（驱动 U-Boot 用）</span></div></div><div><div><div>9</div></div><div><span>│   ├── bootflow2.py       # 脚本化 tftp + bootm 串口序列</span></div></div><div><div><div>10</div></div><div><span>│   ├── mkconfig.sh        # MSYS2 构建步骤</span></div></div><div><div><div>11</div></div><div><span>│   ├── cleanbuild3.sh     # MSYS2 清理重建</span></div></div><div><div><div>12</div></div><div><span>│   ├── mihomobuild.sh     # mihomo 相关构建</span></div></div><div><div><div>13</div></div><div><span>│   ├── initramfs-src/     # init.c + pack2.py</span></div></div><div><div><div>14</div></div><div><span>│   └── README.md</span></div></div><div><div><div>15</div></div><div><span>├── upstream-series/                             # cnjn 的 ZX279133 16 补丁集（6.18.38 基线，参考）</span></div></div><div><div><div>16</div></div><div><span>├── research.md                                 # 内核版本调研笔记</span></div></div><div><div><div>17</div></div><div><span>├── README.md / README_EN.md / README           # 中英文说明 + 内核自带 README</span></div></div><div><div><div>18</div></div><div><span>├── build.sh                                    # Linux / WSL 一键编译</span></div></div><div><div><div>19</div></div><div><span>├── build.bat                                   # Windows / MSYS2 一键编译</span></div></div><div><div><div>20</div></div><div><span>└── .github/workflows/build.yml                 # CI：main + v* tag + 手动触发</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>编译产物由 CI 直接发布到 GitHub Release：<strong>v0.1.1</strong> 已包含 <code>uImage-e2631.img</code>（约 13.3MB，zImage + DTB 拼接，DTB 在末尾），可直接下载后 tftp 引导。</p></section><section><h3>9.2 复现步骤<a href="#92-复现步骤"><span>#</span></a></h3><p>Linux / WSL（树内直接构建，无需套补丁）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/Zlion-Y/e2631-mainline-zx279128s.git</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>cd</span><span> </span><span>e2631-mainline-zx279128s</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>./build.sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>Windows（MSYS2，首次运行自动装 MSYS2 与交叉工具链，再树内构建）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git clone https://github.com/Zlion-Y/e2631-mainline-zx279128s.git</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>cd</span><span> e2631-mainline-zx279128s</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>build.bat</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>GitHub Actions：fork 后 push（或手动 <code>workflow_dispatch</code>）即可，产物在 Actions 页面 / Release 下载。</p><p>编译产物 <code>uImage-e2631.img</code> 按第四章的姿势引导：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tftp</span><span> </span><span>0x43000000</span><span> </span><span>uImage-e2631.img</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>bootm</span><span> </span><span>0x43000000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>提示</div></div><div><p>树内构建要点：<code>make zx279128s_e2631_defconfig</code> 通过 <code>CONFIG_INITRAMFS_SOURCE</code> 把 <code>initramfs-e2631.cpio.gz</code> 一并编入；设备树用单目标编译（<code>make zte/zx279128s-e2631.dtb</code>），不走 <code>make dtbs</code> 批量入口。</p></div></div></section><section><h3>9.3 CI 与 Release<a href="#93-ci-与-release"><span>#</span></a></h3><p><code>build.yml</code> 触发条件：<code>main</code> 分支 push、<code>v*</code> tag push、手动 <code>workflow_dispatch</code>；并显式声明 <code>permissions: contents: write</code> 以便创建/更新 Release。流程为：装依赖 → 装 Arm GNU Toolchain 10.3（<code>arm-none-linux-gnueabihf-</code>）→ <code>zx279128s_e2631_defconfig</code> → <code>zImage</code> → 单目标 <code>zte/zx279128s-e2631.dtb</code> → <code>e2631-tools/pack_uimage.py</code> 打包 → 上传 artifact → 在 tag 上挂到 Release。</p><p>当前状态：<strong>v0.1.1 已发布</strong>（2026-08-31），Release 附件 <code>uImage-e2631.img</code> 为 Actions 验证产物（zImage + DTB），可直接下载引导。</p><div><div><div></div><div>提示</div></div><div><p>后续发新版：<code>git tag v0.1.2</code> 后 <code>git push origin v0.1.2</code>，Actions 会自动构建并更新 Release，无需手动传附件。</p></div></div></section><section><h3>9.4 参考项目<a href="#94-参考项目"><span>#</span></a></h3>

<table><thead><tr><th>项目</th><th>用途</th></tr></thead><tbody><tr><td><a href="https://github.com/cnjn/linux-mainline-zte-zxslc-sr1010" target="_blank">cnjn/linux-mainline-zte-zxslc-sr1010</a></td><td>SR1010 主线移植，本项目的基础（6.18.38 + ZX279133 平台支持）</td></tr><tr><td><a href="https://gitlab.com/xuejian1354/toolchains/" target="_blank">xuejian1354/toolchains</a></td><td>官方 ZX27912X 交叉工具链</td></tr><tr><td><a href="https://github.com/1234205a/zte-sr1010-research" target="_blank">1234205a/zte-sr1010-research</a></td><td>原厂固件分析方法论</td></tr></tbody></table><div><div><div></div><div>提示</div></div><div><p>仓库内 <code>research.md</code> 记录了内核版本调研结论：6.18.38 是 <strong>6.18 LTS 稳定线</strong>的第 38 个补丁版本（2026-07-04 发布，LTS 预计 EOL 2028-12），同线当前最新为 6.18.45；cnjn 分支基于 6.18.38 官方基线加入 ZX279133 平台支持。树内 <code>upstream-series/</code> 保留了该 ZX279133 16 补丁集（6.18.38 基线）作为参考。</p></div></div><hr /></section></section>
<section><h2>十、待办与未来工作<a href="#十待办与未来工作"><span>#</span></a></h2>

<table><thead><tr><th>项目</th><th>说明</th><th>状态</th></tr></thead><tbody><tr><td>有线网卡驱动</td><td>三件套：GEPHY（4 个千兆 PHY）、<code>zx279128s-mdio</code> 控制器、TM/NPP/PP 包处理引擎；参考原厂 <code>tm.ko</code>（1.2MB）/ <code>switch.ko</code>（223KB）逆向，cnjn 的 ZX279133 MDIO 补丁（0008）可作控制器写法参考</td><td>⏳ 下一个目标</td></tr><tr><td>第二核 SMP</td><td>原厂 enable-method <code>zte,zx279128-smp</code>，需逆向上核流程（推测类似 A9 的 NDPGFCR / freeze 寄存器）</td><td>⏳ 未移植</td></tr><tr><td>SPI-NAND + 透明解密</td><td>ZTE SPIFC 控制器 + Denali NAND；flash 分区带 AES-128-ECB 透明解密（密钥由 <code>zte_token</code> 派生）；本移植不碰 flash，不含任何原厂密钥</td><td>⏳ 未移植</td></tr><tr><td>PCIe host（MT7916 WiFi）</td><td><code>zte,ZX279127-pcie</code> 未移植；<code>mt7916e</code> 驱动已编入内核，PCIe 通了 WiFi 即用</td><td>⏳ 未移植</td></tr><tr><td>时钟树</td><td>topcrm / lsp0crpm / lsp1crpm 用 fixed-clock 占位；A9 PERIPHCLK 500MHz 实测可用，外设时钟精度未逐一校准</td><td>⚠️ 占位</td></tr><tr><td>DW MMC</td><td>SD 控制器在原厂 DT 里就是 disabled</td><td>⚠️ DT 已写</td></tr><tr><td>USB host</td><td>dwc2 / dwc3 驱动已验证，但本机型无对外 USB 口、控制器时钟未接，DT 保持禁用</td><td>⚠️ 禁用</td></tr><tr><td>OpenWrt / ImmortalWrt 移植</td><td>需先补齐网卡与闪存驱动</td><td>⏳ 规划中</td></tr></tbody></table><div><div><div></div><div>提示</div></div><div><p>已完成且已上板验证的项还包括：L2C-310 / GIC / 全局定时器（单核 1000 BogoMIPS）；DTS 中 NAND / SPIFC / PCIe / MDIO / GEPHY 节点已描述但未启用；原厂网络命令 ip / iptables / ip6tables / ebtables / tc / dnsmasq / curl 已打包进 initramfs。</p></div></div><hr /></section>
<section><h2>十一、总结<a href="#十一总结"><span>#</span></a></h2><ul>
<li><strong>链路已全部打通</strong>：固件解密 → SoC 逆向 → U-Boot 引导 → 主线 6.18.38 → busybox shell → mihomo 适配 → 开源发布；</li>
<li><strong>关键经验</strong>：XIP 加载陷阱、<code>0x42000000</code> 禁区、强制 <code>-marm</code>、<code>-j4</code> 上限、CRLF 行尾、嵌入式 initramfs、armv5 软浮点；</li>
<li><strong>硬件上限</strong>：无 VFP / NEON / idiv，代理加解密是 CPU 软算，跑满速会有明显瓶颈，但已做到不非法指令、特性齐全；</li>
<li><strong>可复现</strong>：源码、设备树、配置、工具、双平台一键脚本、CI 全部在仓库中，拉下来就能编，改完还能继续编。</li>
</ul><hr /><blockquote><p><strong>文档版本：</strong> 2026-08-31
<strong>适用系统：</strong> Windows 10/11（MSYS2 构建） / WSL2
<strong>适用平台：</strong> 中兴 ZTE ZXHN E2631（巡天 AX3000）</p></blockquote></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/tcl-tv-adb-install-unlock/</id>
      <title type="text">TCL/雷鸟电视 ADB 安装拦截排查与永久放行</title>
      <published>2026-08-28T00:00:00.000Z</published>
      <updated>2026-09-05T00:00:00.000Z</updated>
      <author><name>Shiyan</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/tcl-tv-adb-install-unlock/"/>
      <summary type="text">定位 TCL/雷鸟电视系统安装器拦截 ADB 安装的验证组件，通过改写 InstallConfig 配置实现免禁用、免脚本的永久放行</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档记录一次针对 TCL 电视（mt5879 方案，Android 11）「ADB 无法安装应用，必须先禁用安装器」问题的完整排查过程与最终解法。
<strong>适用设备：</strong> TCL 电视（mt5879_cn 方案，Android 11，其他 TCL/雷鸟机型可参考）
<strong>适用人群：</strong> 会使用 ADB 的电视用户 / 运维玩家</p></blockquote>
<hr />
<section><h2>1. 问题现象<a href="#1-问题现象"><span>#</span></a></h2><p>TCL 电视通过 Wi-Fi ADB 连接后，安装应用直接报错：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>connect</span><span> </span><span>192.168.5.6</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>adb</span><span> </span><span>install</span><span> </span><span>xxx.apk</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>报错内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>adb.exe: failed to install xxx.apk: Failure [INSTALL_FAILED_VERIFICATION_FAILURE]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只有先禁用系统安装器 <code>com.android.packageinstaller</code> 才能安装；但安装完成后<strong>必须重新启用</strong>，否则电视无法开机。整个流程繁琐且有翻车风险。</p><div><div><div></div><div>警告</div></div><div><p><code>com.android.packageinstaller</code> 是开机关键组件，任何时候都不要让电视在「安装器被禁用」的状态下重启。</p></div></div><hr /></section>
<section><h2>2. 排查过程<a href="#2-排查过程"><span>#</span></a></h2><section><h3>2.1 缩小范围：排除 TCL 安全守护<a href="#21-缩小范围排除-tcl-安全守护"><span>#</span></a></h3><p>先怀疑的是 TCL 安全守护 <code>com.tcl.guard</code>，但实测它的启停与拦截无关；只有禁用 <code>com.android.packageinstaller</code> 才能放行安装，说明拦截逻辑在安装器应用自身。</p></section><section><h3>2.2 复现拦截并抓取日志<a href="#22-复现拦截并抓取日志"><span>#</span></a></h3><p>保持安装器启用状态，安装一个真实 APK 并抓取系统日志：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>logcat</span><span> </span><span>-c</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>adb</span><span> </span><span>install</span><span> </span><span>-r</span><span> </span><span>xxx.apk</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>adb</span><span> </span><span>logcat</span><span> </span><span>-d</span><span> &gt; </span><span>block.log</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>日志中出现关键线索：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Start proc com.android.packageinstaller for broadcast</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>{com.android.packageinstaller/com.android.packageinstaller.verifier.VerifierReciver}</span></div></div><div><div><div>3</div></div><div><span>Foreground service started ...: service com.android.packageinstaller/.verifier.StrategyService</span></div></div><div><div><div>4</div></div><div><span>Config:Grabber:InstallConfig: Currrent content from Db is ：[{"enable":"true","strategies":[...]}]</span></div></div><div><div><div>5</div></div><div><span>D ConfigProvider: feature:content://com.tcl.providers.config/InstallConfig</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>拦截者浮出水面：<strong>TCL 在系统安装器里内置了一个验证组件</strong>，其策略来自一个内容提供者。</p></section><section><h3>2.3 拉取安装器 APK 分析<a href="#23-拉取安装器-apk-分析"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>shell</span><span> </span><span>pm</span><span> </span><span>path</span><span> </span><span>com.android.packageinstaller</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>MSYS_NO_PATHCONV</span><span>=</span><span>1</span><span> </span><span>adb</span><span> </span><span>pull</span><span> </span><span>/system/priv-app/PackageInstaller/PackageInstaller.apk</span><span> </span><span>.</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>unzip</span><span> </span><span>-o</span><span> </span><span>-q</span><span> </span><span>PackageInstaller.apk</span><span> </span><span>-d</span><span> </span><span>apk</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>提取 dex 字符串确认：</p><ul>
<li><code>com.android.packageinstaller.verifier.VerifierReciver</code> 监听 <code>android.intent.action.PACKAGE_NEEDS_VERIFICATION</code>，持有 <code>android.permission.PACKAGE_VERIFICATION_AGENT</code> 权限（全系统仅它持有）；</li>
<li><code>StrategyService</code> 每次安装时从 <code>content://com.tcl.providers.config/InstallConfig</code> 读取策略 JSON。</li>
</ul><p>查询该配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>shell</span><span> </span><span>content</span><span> </span><span>query</span><span> </span><span>--uri</span><span> </span><span>content://com.tcl.providers.config/InstallConfig</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Row: 0 project_id=InstallConfig, config_content=[{"enable":"true","strategies":[</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>{"name":"safeStrategy","enable":"true","priority":"0"},</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>{"name":"blackListStrategy","enable":"true","packages":[],"priority":"1"},</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>{"name":"thridStrategy","enable":"false","packages":[...],"priority":"2"},</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>{"name":"launcherStrategy","enable":"true","packages":[],"priority":"3"},</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>{"name":"overDueStrategy","enable":"true","packages":[],"priority":"4"},</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>{"name":"pmStrategy","enable":"true","priority":"5"}]}]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>重要</div></div><div><p>组件级禁用（<code>pm disable-user com.android.packageinstaller/.verifier.VerifierReciver</code>）会被系统拒绝（<code>Shell cannot change component state</code>），但 shell 对这个配置提供者<strong>有写权限</strong>——这就是突破口。</p></div></div><hr /></section></section>
<section><h2>3. 拦截机制原理<a href="#3-拦截机制原理"><span>#</span></a></h2><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>enable=true 且策略生效</p></span></div><div><span></span></div><div><span><p>enable=false 无策略可用</p></span></div><div><span></span></div><div><span><p>adb install</p></span></div><div><span><p>PackageManagerService 创建安装会话</p></span></div><div><span><p>发送 PACKAGE_NEEDS_VERIFICATION 广播</p></span></div><div><span><p>VerifierReciver 接收</p></span></div><div><span><p>StrategyService 启动</p></span></div><div><span><p>读取 InstallConfig</p></span></div><div><span><p>verifyPendingInstall 拒绝</p></span></div><div><span><p>INSTALL_FAILED_VERIFICATION_FAILURE</p></span></div><div><span><p>不返回拒绝裁决</p></span></div><div><span><p>安装继续，Success</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>enable=true 且策略生效</p></span></div><div><span></span></div><div><span><p>enable=false 无策略可用</p></span></div><div><span></span></div><div><span><p>adb install</p></span></div><div><span><p>PackageManagerService 创建安装会话</p></span></div><div><span><p>发送 PACKAGE_NEEDS_VERIFICATION 广播</p></span></div><div><span><p>VerifierReciver 接收</p></span></div><div><span><p>StrategyService 启动</p></span></div><div><span><p>读取 InstallConfig</p></span></div><div><span><p>verifyPendingInstall 拒绝</p></span></div><div><span><p>INSTALL_FAILED_VERIFICATION_FAILURE</p></span></div><div><span><p>不返回拒绝裁决</p></span></div><div><span><p>安装继续，Success</p></span></div>
</div></div></div><p>要点：</p>

<table><thead><tr><th>组件</th><th>作用</th></tr></thead><tbody><tr><td><code>com.android.packageinstaller</code></td><td>TCL 改造的系统安装器，同时是开机关键组件</td></tr><tr><td><code>.verifier.VerifierReciver</code></td><td>接收系统安装验证广播</td></tr><tr><td><code>.verifier.StrategyService</code></td><td>按策略决定放行/拒绝</td></tr><tr><td><code>com.tcl.providers.config/InstallConfig</code></td><td>策略配置存储，shell 可读写</td></tr></tbody></table><hr /></section>
<section><h2>4. 永久放行方案<a href="#4-永久放行方案"><span>#</span></a></h2><p>把 <code>InstallConfig</code> 的总开关改写为 <code>false</code>，验证器读不到可用策略即放行。<strong>安装器全程保持启用，开机无影响，重启后依然生效。</strong></p><div><div><div></div><div>电脑 / 手机两个渠道任选其一</div></div><div><p>下面给出电脑（PowerShell）和手机（甲壳虫 ADB）两种写法，<strong>效果完全相同，选一个执行即可</strong>，不要都执行。
手机端有一个前置条件：先在甲壳虫里连接电视并<strong>确认设备在线</strong>。连接断开后输入的任何命令都会报 <code>no devices/emulators found</code>，且报错会被界面吞掉——这是「命令没生效」最常见的原因。</p></div></div><p><strong>电脑（PowerShell）</strong>——第一条改配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>adb shell content update </span><span>--</span><span>uri content:</span><span>//</span><span>com.tcl.providers.config</span><span>/</span><span>InstallConfig </span><span>--</span><span>bind config_content:s:false</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>电脑（PowerShell）</strong>——第二条重启验证器：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>adb shell am force</span><span>-</span><span>stop com.android.packageinstaller</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>手机（甲壳虫 ADB）</strong>——第一条改配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>content</span><span> </span><span>update</span><span> </span><span>--uri</span><span> </span><span>content://com.tcl.providers.config/InstallConfig</span><span> </span><span>--bind</span><span> </span><span>config_content:s:</span><span>false</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>手机（甲壳虫 ADB）</strong>——第二条重启验证器：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>am</span><span> </span><span>force-stop</span><span> </span><span>com.android.packageinstaller</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>建议</div></div><div><p>两个渠道的命令都设计成零引号：<code>false</code> 值不含引号和冒号，解析器读到它同样走「解析异常 → 无策略 → 放行」路径，效果与完整 JSON 等价。<strong>电脑端也用同款零引号写法</strong>——Windows PowerShell 5.1 与 7 给外部程序传参的行为不同，带多层引号的长命令在 5.1 上会丢引号导致写入坏数据，零引号写法在两个版本行为完全一致。<strong>每条命令执行前都确认设备在线。</strong></p></div></div><p>验证写入结果：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>shell</span><span> </span><span>content</span><span> </span><span>query</span><span> </span><span>--uri</span><span> </span><span>content://com.tcl.providers.config/InstallConfig</span><span> </span><span>--projection</span><span> </span><span>config_content</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出应为（电脑 / 手机端相同）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Row: 0 config_content=false</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>之后安装直接：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>install</span><span> </span><span>xxx.apk</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>提示</div></div><div><p><code>content</code> 命令的 <code>--bind</code> 参数按冒号分段解析，任何含 JSON 的命令里冒号都必须转义——这也是第 5 节恢复命令比解锁复杂的原因。解锁因为用了零引号的 <code>false</code> 值，两个渠道都天然免疫此问题。</p></div></div><hr /></section>
<section><h2>5. 恢复原始拦截<a href="#5-恢复原始拦截"><span>#</span></a></h2><p>想恢复 TCL 原始策略时，把配置写回<strong>完整的原厂策略数组</strong>即可。</p><div><div><div></div><div>注意</div></div><div><p>恢复<strong>必须</strong>写回下面完整的策略 JSON。只写 <code>{"enable":"true"}</code> 这种短形式，解析器照样按异常处理，拦截并不会恢复。恢复命令含引号和转义，若在手机端执行失败，改用电脑端即可（不影响已解锁状态）。</p></div></div><p><strong>电脑（PowerShell）</strong>——本地生成恢复脚本 → 推送到电视 → 执行（三行按顺序，PowerShell 5.1 和 7 均实测可用）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>Set-Content</span><span><span> </span><span>-</span><span>NoNewline </span><span>-</span><span>Encoding Ascii </span></span><span>$env:TEMP</span><span>\restore.sh </span><span>'content update --uri content://com.tcl.providers.config/InstallConfig --bind </span><span>''</span><span>config_content:s:[{"enable"\:"true","strategies"\:[{"name"\:"safeStrategy","enable"\:"true","priority"\:"0"},{"name"\:"blackListStrategy","enable"\:"true","packages"\:[],"priority"\:"1"},{"name"\:"thridStrategy","enable"\:"false","packages"\:["com.dangbeimarket","com.shafa.market","com.ant.store.appstore"],"priority"\:"2"},{"name"\:"launcherStrategy","enable"\:"true","packages"\:[],"priority"\:"3"},{"name"\:"overDueStrategy","enable"\:"true","packages"\:[],"priority"\:"4"},{"name"\:"pmStrategy","enable"\:"true","priority"\:"5"}]}]</span><span>''</span><span> --where "project_id=</span><span>''</span><span>InstallConfig</span><span>''</span><span>"'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb push </span><span>$env:TEMP</span><span><span>\restore.sh </span><span>/</span></span><span>data</span><span><span>/</span><span>local</span><span>/</span><span>tmp</span><span>/</span><span>restore.sh</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>adb shell sh </span><span>/</span></span><span>data</span><span><span>/</span><span>local</span><span>/</span><span>tmp</span><span>/</span><span>restore.sh</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>手机（甲壳虫 ADB）：</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>content</span><span> </span><span>update</span><span> </span><span>--uri</span><span> </span><span>content://com.tcl.providers.config/InstallConfig</span><span> </span><span>--bind</span><span> </span><span>'config_content:s:[{"enable"\:"true","strategies"\:[{"name"\:"safeStrategy","enable"\:"true","priority"\:"0"},{"name"\:"blackListStrategy","enable"\:"true","packages"\:[],"priority"\:"1"},{"name"\:"thridStrategy","enable"\:"false","packages"\:["com.dangbeimarket","com.shafa.market","com.ant.store.appstore"],"priority"\:"2"},{"name"\:"launcherStrategy","enable"\:"true","packages"\:[],"priority"\:"3"},{"name"\:"overDueStrategy","enable"\:"true","packages"\:[],"priority"\:"4"},{"name"\:"pmStrategy","enable"\:"true","priority"\:"5"}]}]'</span><span> </span><span>--where</span><span> </span><span>"project_id='InstallConfig'"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>恢复后同样重启验证器：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>am</span><span> </span><span>force-stop</span><span> </span><span>com.android.packageinstaller</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>验证：查询命令同第 4 节，输出应为 <code>[{"enable":"true","strategies":[...]}]</code>。</p><p>原始配置全文（备份用）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[{</span><span>"enable"</span><span>:</span><span>"true"</span><span>,</span><span>"strategies"</span><span>:[{</span><span>"name"</span><span>:</span><span>"safeStrategy"</span><span>,</span><span>"enable"</span><span>:</span><span>"true"</span><span>,</span><span>"priority"</span><span>:</span><span>"0"</span><span>},{</span><span>"name"</span><span>:</span><span>"blackListStrategy"</span><span>,</span><span>"enable"</span><span>:</span><span>"true"</span><span>,</span><span>"packages"</span><span>:[],</span><span>"priority"</span><span>:</span><span>"1"</span><span>},{</span><span>"name"</span><span>:</span><span>"thridStrategy"</span><span>,</span><span>"enable"</span><span>:</span><span>"false"</span><span>,</span><span>"packages"</span><span>:[</span><span>"com.dangbeimarket"</span><span>,</span><span>"com.shafa.market"</span><span>,</span><span>"com.ant.store.appstore"</span><span>],</span><span>"priority"</span><span>:</span><span>"2"</span><span>},{</span><span>"name"</span><span>:</span><span>"launcherStrategy"</span><span>,</span><span>"enable"</span><span>:</span><span>"true"</span><span>,</span><span>"packages"</span><span>:[],</span><span>"priority"</span><span>:</span><span>"3"</span><span>},{</span><span>"name"</span><span>:</span><span>"overDueStrategy"</span><span>,</span><span>"enable"</span><span>:</span><span>"true"</span><span>,</span><span>"packages"</span><span>:[],</span><span>"priority"</span><span>:</span><span>"4"</span><span>},{</span><span>"name"</span><span>:</span><span>"pmStrategy"</span><span>,</span><span>"enable"</span><span>:</span><span>"true"</span><span>,</span><span>"priority"</span><span>:</span><span>"5"</span><span>}]}]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>6. 兜底方案：禁用-安装-恢复<a href="#6-兜底方案禁用-安装-恢复"><span>#</span></a></h2><p>若上述配置方案因固件更新失效，可回退到传统方案：禁用安装器 → 安装 → 恢复。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>shell</span><span> </span><span>pm</span><span> </span><span>disable-user</span><span> </span><span>com.android.packageinstaller</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>adb</span><span> </span><span>install</span><span> </span><span>-r</span><span> </span><span>xxx.apk</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>adb</span><span> </span><span>shell</span><span> </span><span>pm</span><span> </span><span>enable</span><span> </span><span>com.android.packageinstaller</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>注意</div></div><div><p>禁用后务必确认恢复：<code>adb shell "dumpsys package com.android.packageinstaller"</code> 输出 <code>enabled=1</code> 才是启用状态（<code>enabled=3</code> 为禁用）。电视在安装器禁用状态下重启可能无法开机。</p></div></div><p>核心兜底逻辑（PowerShell 脚本要点）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>try</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>adb shell pm </span><span>disable-user</span><span> com.android.packageinstaller</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>adb install </span><span>-</span><span>r </span><span>$apk</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div><span>finally</span><span> {</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>adb shell pm enable com.android.packageinstaller</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>try/finally</code> 保证即使安装失败、甚至 ADB 中途断线，恢复命令也会执行。</p><hr /></section>
<section><h2>7. 故障排查<a href="#7-故障排查"><span>#</span></a></h2><p><strong>Q：写入命令报 <code>Binding not well formed</code>？</strong></p><p>JSON 里的冒号转义错了位：手机端恢复命令的冒号必须写成 <code>\:</code>（单反斜杠），写成 <code>\\:</code>（双反斜杠）反而会报这个错。电脑端恢复用「本地脚本 + push」方式，转义已内置，无需手改。解锁则永远可以用零引号写法，天然免疫此问题。</p><p><strong>Q：手机端命令执行了但毫无反应？</strong></p><p>甲壳虫与电视的连接已断开。先在 app 里重连 <code>192.168.5.6:5555</code> 并确认设备在线，再执行命令；每条命令执行前都确认在线。</p><p><strong>Q：写入成功但仍报 <code>INSTALL_FAILED_VERIFICATION_FAILURE</code>？</strong></p><p>验证器进程缓存了旧配置，强制重启它：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb</span><span> </span><span>shell</span><span> </span><span>am</span><span> </span><span>force-stop</span><span> </span><span>com.android.packageinstaller</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>Q：过了一段时间又被拦截了？</strong></p><p>TCL 云端配置同步可能覆盖了本地配置，重新执行第 4 节的写入命令即可。可先用第 4 节的查询命令确认当前配置内容。</p><p><strong>Q：<code>adb devices</code> 显示 <code>unauthorized</code>？</strong></p><p>在电视屏幕上找到「允许 USB 调试」弹窗，勾选「一律允许」并确认；若电视无弹窗，重插网络 ADB 连接触发。</p><hr /><blockquote><p><strong>文档版本：</strong> 2026-09-05
<strong>适用设备：</strong> TCL 电视 mt5879_cn 方案（Android 11），其他 TCL/雷鸟机型可参考
<strong>适用平台：</strong> Wi-Fi ADB / USB ADB</p></blockquote></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/z503-to-public/</id>
      <title type="text">Z500/Z503/Z507升级小方糖公版系统</title>
      <published>2026-08-28T00:00:00.000Z</published>
      <updated>2026-08-28T00:00:00.000Z</updated>
      <author><name>zlion</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/z503-to-public/"/>
      <summary type="text">记录 Z503 通过 Telnet 扩容分区，升级为公版的过程.</summary>
      <content type="html"><![CDATA[<section><h2>说明<a href="#说明"><span>#</span></a></h2><p>该教程由<a href="https://www.right.com.cn/forum/?639610" target="_blank">@cnjn</a>首发于恩山论坛：<a href="https://www.right.com.cn/forum/thread-8480346-1-1.html" target="_blank">中兴小方糖 Z503 刷 Z506 固件</a>，这里进行优化，适用于小白操作。</p></section>
<section><h2>准备工作<a href="#准备工作"><span>#</span></a></h2><ul>
<li><a href="https://wwanu.lanzoue.com/ijPLK3vbfl1i" target="_blank">Z506官方固件</a></li>
<li><a href="https://wwbnc.lanzoub.com/iuDNi451dula" target="_blank">一键开启Telnet脚本</a></li>
<li><a href="https://wwbnc.lanzoub.com/iP5mZ451e9hg" target="_blank">一键修改Tag脚本</a></li>
<li>MobaXterm终端工具</li>
</ul></section>
<section><h2>操作步骤<a href="#操作步骤"><span>#</span></a></h2><section><h3>1. 开启Telnet<a href="#1-开启telnet"><span>#</span></a></h3><p>从路由器网页管理后台导出配置文件<code>config.bin</code>，与<code>一键开启Telnet脚本</code>放在一起，双击脚本运行得到<code>config-telnet-enable.bin</code>，将其再从管理后台还原回去，路由器会自动重启，Telnet就打开了，默认的账密为admin。</p></section><section><h3>2. 扩容分区<a href="#2-扩容分区"><span>#</span></a></h3><p>使用MobaXterm新建Telnet会话连接路由器，执行下面的命令，扩容 /var 目录。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mount</span><span> </span><span>-o</span><span> </span><span>remount,size=32M</span><span> </span><span>/var</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3. 修改设备型号<a href="#3-修改设备型号"><span>#</span></a></h3><p>将设备型号Z500/Z503/Z507通通改为中兴的公版型号Z506。</p><p>设置MobaXterm的TFTP服务
</p><figure><img alt="TFTP 目录配置参考" loading="lazy" width="1363" height="1016" src="/_astro/TFTP.Dore2Zdg_8bPMI.webp" /><figcaption>TFTP 目录配置参考</figcaption></figure><p></p><p>命令中的 <code>192.168.5.7</code>请替换为电脑当前的局域网 IP。</p><section><h4>3.1 提取Tag分区<a href="#31-提取tag分区"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/dev/mtdblock2</span><span> &gt; </span><span>/tmp/tag.bin</span></div></div><div><div><div>2</div></div><div><span>tftp</span><span> </span><span>-p</span><span> </span><span>-l</span><span> </span><span>/tmp/tag.bin</span><span> </span><span>-r</span><span> </span><span>tag.bin</span><span> </span><span>192.168.5.7</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>3.2 修改Tag分区<a href="#32-修改tag分区"><span>#</span></a></h4><p>把提取出来的tag.bin与<code>一键修改Tag脚本</code>放在一起双击脚本，得到<code>tag-Z506.bin</code>文件，放到TFTP目录中。</p></section><section><h4>3.3 写入Tag分区<a href="#33-写入tag分区"><span>#</span></a></h4><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tftp</span><span> </span><span>-g</span><span> </span><span>-l</span><span> </span><span>/tmp/tag.bin</span><span> </span><span>-r</span><span> </span><span>tag-E2631.bin</span><span> </span><span>192.168.5.7</span></div></div><div><div><div>2</div></div><div><span>cat</span><span> </span><span>/tmp/tag.bin</span><span> &gt; </span><span>/dev/mtdblock2</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>4. 上传公版固件<a href="#4-上传公版固件"><span>#</span></a></h3><p>进入路由器管理后台，找到系统更新固件上传<code>zxhnze50x_hv100_fv1002b28000_firmware-20230615093954.bin</code> ,确认升级即可。重启完毕后就成功刷为公版系统了。</p></section><section><h3>5. 重置<a href="#5-重置"><span>#</span></a></h3><p>升级完成后可以恢复出厂设置一次。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/design-a-tv-remote-app/</id>
      <title type="text">设计遥控器：一款 Android TV 手机遥控 App 的设计实录</title>
      <published>2026-08-24T00:00:00.000Z</published>
      <updated>2026-08-24T00:00:00.000Z</updated>
      <author><name>Shiyan</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/design-a-tv-remote-app/"/>
      <summary type="text">从设计目标、iOS 16 设计语言到触控板与浮动光标的交互方案，完整复盘一款手机遥控 Android TV App 的设计过程。</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档面向想要设计「手机遥控电视」类 App 的产品与开发人员，涵盖设计目标、视觉语言、信息架构、核心交互与踩坑经验。
<strong>适用系统：</strong> Android 8.0+（TV 端）/ Android 8.0+（手机端）
<strong>适用平台：</strong> 智能电视 / 电视盒子 + 安卓手机
</p><figure><img alt="手机遥控电视概念图" loading="lazy" width="1536" height="1024" src="/_astro/hero.BpmyQNt-_1ck8NV.webp" /><figcaption>手机遥控电视概念图</figcaption></figure><p></p></blockquote>
<section><h2>1. 为什么要重新设计遥控器<a href="#1-为什么要重新设计遥控器"><span>#</span></a></h2><p>智能电视的遥控器越做越短：按键从三十多个砍到十几个，最后只剩一个十字键加几个功能键。但电视上的内容越来越复杂——应用商店、设置菜单、登录页面、验证码输入框，都在逼着用户在「十字键一格一格挪」里浪费时间。</p><p>实体遥控器的物理极限暴露无遗：</p>

<table><thead><tr><th>场景</th><th>实体遥控器</th><th>手机遥控</th></tr></thead><tbody><tr><td>搜索片名</td><td>十字键逐字选择</td><td>直接打字</td></tr><tr><td>安装应用</td><td>逐格导航到安装按钮</td><td>手机上点一下推送</td></tr><tr><td>输入密码</td><td>虚拟键盘逐字符移动</td><td>手机键盘直接输入</td></tr><tr><td>精准点击小按钮</td><td>几乎不可能</td><td>触控板 + 光标</td></tr></tbody></table><p>于是我们决定做一款手机遥控 Android TV 的 App，对标市面上的八爪鱼遥控：<strong>TV 端 + 手机端双端架构</strong>，手机负责输入与展示，TV 端负责接收指令并注入系统事件。</p></section>
<section><h2>2. 设计目标与约束<a href="#2-设计目标与约束"><span>#</span></a></h2><p>设计开始前，先把约束摆清楚。约束不是敌人，它决定了方案的下限。</p><section><h3>2.1 产品功能范围<a href="#21-产品功能范围"><span>#</span></a></h3><p>六大核心功能一个都不能少：</p><ol>
<li>基础遥控（方向、确认、返回、主页、菜单、音量、电源）</li>
<li>App 安装推送</li>
<li>远程输入法（IME）</li>
<li>电视清理 / 加速</li>
<li>远程应用管理（启动 / 卸载）</li>
<li>基于传感器的 TV 端浮动光标</li>
</ol></section><section><h3>2.2 技术约束<a href="#22-技术约束"><span>#</span></a></h3><div><div><div></div><div>重要</div></div><div><p>TV 端无法开启 Accessibility 服务（部分厂商环境限制），ADB 路线也被明确排除。输入注入必须依赖 AccessibilityService 或厂商私有插件，这直接决定了浮动光标必须做成「虚拟光标」而非系统鼠标。</p></div></div><ul>
<li>手机与电视在同一局域网，走 HTTP（TCP）+ UDP 自研协议</li>
<li>TV 端覆盖层窗口必须由 AccessibilityService 上下文创建</li>
<li>传感器数据驱动光标，位移采用 0..1000 归一化坐标，TV 端按分辨率缩放</li>
</ul></section><section><h3>2.3 设计目标<a href="#23-设计目标"><span>#</span></a></h3><p>一句话：<strong>让用户拿起手机的三秒内，就能完成一次电视操作。</strong></p></section></section>
<section><h2>3. 设计语言：为什么选 iOS 16 风格<a href="#3-设计语言为什么选-ios-16-风格"><span>#</span></a></h2><p>遥控器 App 是一个「工具」，工具的最高境界是被忽略——用户看的是电视，不是手机。因此视觉上追求极简、安静、零学习成本。</p><p>最终确定的设计规范：</p>

<table><thead><tr><th>元素</th><th>规范</th></tr></thead><tbody><tr><td>背景色</td><td><code>#F2F2F7</code>（iOS 分组式灰底）</td></tr><tr><td>卡片圆角</td><td>14dp，白色卡片浮在灰底上</td></tr><tr><td>图标</td><td>线性图标（stroke 风格），不用填充图标</td></tr><tr><td>导航</td><td>底部 Tab Bar，四个入口</td></tr><tr><td>特效</td><td>毛玻璃（半透明 + 模糊），仅用于顶栏与 Tab Bar</td></tr><tr><td>阴影</td><td>无阴影，靠灰底与白卡的对比分层</td></tr></tbody></table><div><div><div></div><div>警告</div></div><div><p>明确禁用一切 Material Design 元素：FAB 悬浮按钮、ripple 水波纹、hamburger 抽屉菜单、投影卡片。这些元素一旦混入，整个界面会立刻「变味」，视觉一致性是这类工具 App 的生命线。</p></div></div><p>设计语言统一的好处立竿见影：遥控页、应用页、工具页、我的页四个页面，用户在任何一页都能凭肌肉记忆找到返回方式与操作入口。</p><p></p><figure><img alt="遥控页 UI 设计稿" loading="lazy" width="1024" height="1536" src="/_astro/ui-mockup.DSU3kmhK_rvD5D.webp" /><figcaption>遥控页 UI 设计稿</figcaption></figure><p></p></section>
<section><h2>4. 信息架构：四个底部 Tab<a href="#4-信息架构四个底部-tab"><span>#</span></a></h2><p>信息架构遵循「高频前置、低频收敛」原则：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>遥控 App</p></span></div><div><span><p>遥控 Tab<br /><br />方向键 / 触控板 / 音量</p></span></div><div><span><p>应用 Tab<br /><br />安装推送 / 启动卸载</p></span></div><div><span><p>工具 Tab<br /><br />清理加速 / 输入法</p></span></div><div><span><p>我的 Tab<br /><br />设备管理 / 设置</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>遥控 App</p></span></div><div><span><p>遥控 Tab<br /><br />方向键 / 触控板 / 音量</p></span></div><div><span><p>应用 Tab<br /><br />安装推送 / 启动卸载</p></span></div><div><span><p>工具 Tab<br /><br />清理加速 / 输入法</p></span></div><div><span><p>我的 Tab<br /><br />设备管理 / 设置</p></span></div>
</div></div></div><ul>
<li><strong>遥控 Tab</strong>：默认首屏，打开 App 即用，承载 80% 的高频操作</li>
<li><strong>应用 Tab</strong>：TV 端应用列表镜像，支持远程启动、卸载、推送安装</li>
<li><strong>工具 Tab</strong>：清理加速、远程输入法等中低频功能</li>
<li><strong>我的 Tab</strong>：设备连接状态、版本信息、设置项</li>
</ul><p>底部 Tab Bar 采用毛玻璃效果，内容滚动时图标下若隐若现，既保持层次又不喧宾夺主。</p></section>
<section><h2>5. 遥控页：一块屏幕装下整个遥控器<a href="#5-遥控页一块屏幕装下整个遥控器"><span>#</span></a></h2><p>遥控页是整个 App 的核心，布局从上到下分为四层：</p><ol>
<li><strong>设备状态条</strong>：当前连接的电视名称与信号状态，一眼确认「我连的是哪台电视」</li>
<li><strong>触控板区</strong>：大面积手势区域，滑动即移动 TV 端光标，点按即确认</li>
<li><strong>十字键区</strong>：圆形 D-pad，中心 OK 键，四向箭头线性图标</li>
<li><strong>功能键排</strong>：返回、主页、菜单、音量加减、电源，一行排列</li>
</ol><section><h3>5.1 按键布局的取舍<a href="#51-按键布局的取舍"><span>#</span></a></h3><p>实体遥控器按 键隔离 布局，手机屏幕按 使用频率 布局：</p><ul>
<li>方向与确认是最高频操作，占据视觉中心</li>
<li>音量键做成横向长条，天然对应「左右/上下滑动调音量」的心智模型</li>
<li>电源键放在角落，防止误触——误触电源把电视关了，是这类 App 差评的重灾区</li>
</ul><div><div><div></div><div>建议</div></div><div><p>所有按键的点击热区不小于 48dp，宁可视觉留白多一点，也不要让用户在大屏手机上出现「按不中」的情况。</p></div></div></section><section><h3>5.2 按键到协议的映射<a href="#52-按键到协议的映射"><span>#</span></a></h3><p>每个按键最终都会转成一条 HTTP 指令发往 TV 端：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"type"</span><span>: </span><span>"key"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"action"</span><span>: </span><span>"down_up"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"keycode"</span><span>: </span><span>82</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常用键值映射：</p>

<table><thead><tr><th>手机按键</th><th>keycode</th><th>说明</th></tr></thead><tbody><tr><td>返回</td><td>4</td><td>KEYCODE_BACK</td></tr><tr><td>主页</td><td>3</td><td>KEYCODE_HOME</td></tr><tr><td>菜单</td><td>82</td><td>KEYCODE_MENU</td></tr><tr><td>音量加</td><td>24</td><td>KEYCODE_VOLUME_UP</td></tr><tr><td>音量减</td><td>25</td><td>KEYCODE_VOLUME_DOWN</td></tr><tr><td>电源</td><td>26</td><td>KEYCODE_POWER</td></tr></tbody></table></section></section>
<section><h2>6. 触控板：从绝对坐标到相对位移<a href="#6-触控板从绝对坐标到相对位移"><span>#</span></a></h2><p>触控板是设计迭代中最典型的一次「推倒重来」。</p><section><h3>6.1 第一版：绝对坐标（失败）<a href="#61-第一版绝对坐标失败"><span>#</span></a></h3><p>最初的设计是把手指位置直接映射到 TV 屏幕坐标：手指按在触控板左上角，TV 端光标就出现在屏幕左上角。</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>手指按下 x,y</p></span></div><div><span><p>归一化 0..1000</p></span></div><div><span><p>HTTP POST /api/cursor</p></span></div><div><span><p>TV 端光标跳到绝对位置</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>手指按下 x,y</p></span></div><div><span><p>归一化 0..1000</p></span></div><div><span><p>HTTP POST /api/cursor</p></span></div><div><span><p>TV 端光标跳到绝对位置</p></span></div>
</div></div></div><p>真机测试发现致命问题：<strong>触控板面积只有巴掌大，映射到 55 寸电视上，精度完全不够</strong>——想点一个设置按钮，手指 1mm 的抖动在电视上就是几十像素的偏移，根本点不中。</p></section><section><h3>6.2 第二版：相对位移 + 虚拟鼠标（成功）<a href="#62-第二版相对位移--虚拟鼠标成功"><span>#</span></a></h3><p>改为笔记本电脑触控板的心智模型：<strong>TV 端自己维护一个光标位置，手机只上报位移量</strong>。</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>手指滑动 delta</p></span></div><div><span><p>POST /api/mouse dx,dy</p></span></div><div><span><p>TV 端光标位置 + delta</p></span></div><div><span><p>滑一次 光标动一点 位置保持</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>手指滑动 delta</p></span></div><div><span><p>POST /api/mouse dx,dy</p></span></div><div><span><p>TV 端光标位置 + delta</p></span></div><div><span><p>滑一次 光标动一点 位置保持</p></span></div>
</div></div></div>

<table><thead><tr><th>对比项</th><th>绝对坐标</th><th>相对位移</th></tr></thead><tbody><tr><td>精度</td><td>手指抖动被放大</td><td>多滑几次即可微调</td></tr><tr><td>心智模型</td><td>直接点屏幕</td><td>笔记本触控板</td></tr><tr><td>抬手后光标</td><td>跟随消失</td><td>保持原位</td></tr><tr><td>实现复杂度</td><td>低</td><td>中（TV 端维护光标）</td></tr></tbody></table><p>协议设计：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"dx"</span><span>: </span><span>120</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"dy"</span><span>: </span><span>-45</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"show"</span><span>: </span><span>true</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>dx</code> / <code>dy</code>：0..1000 归一化相对位移，TV 端按屏幕分辨率缩放为像素</li>
<li><code>show</code>：<code>true</code> 显示光标，<code>false</code> 隐藏，缺省保持当前状态</li>
</ul><p>抬手后光标保持原位这一点尤为关键——用户可以分多次滑动，把光标「挪」到目标上再点按确认，这正是触控板体验的精髓。</p></section></section>
<section><h2>7. 浮动光标：倾斜手机即可移动光标<a href="#7-浮动光标倾斜手机即可移动光标"><span>#</span></a></h2><p>触控板解决了「精准」，但还有更极致的输入方式：<strong>手机拿起来，往哪边倾，光标往哪边走</strong>。</p><section><h3>7.1 传感器选型<a href="#71-传感器选型"><span>#</span></a></h3><p>手机竖直持握时，加速度计三个轴的物理含义：</p>

<table><thead><tr><th>轴</th><th>竖直持握时含义</th></tr></thead><tbody><tr><td>X 轴 (ax)</td><td>左右倾斜量</td></tr><tr><td>Y 轴 (ay)</td><td>重力（约 9.8 m/s²）</td></tr><tr><td>Z 轴 (az)</td><td>前后倾斜量</td></tr></tbody></table><p>光标速度映射：</p><span><span><span>dx=(ax−ax0)⋅k,dy=(az−az0)⋅kdx = (a_x - a_{x0}) \cdot k, \quad dy = (a_z - a_{z0}) \cdot k</span><span><span><span></span><span>d</span><span>x</span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span><span>a</span><span><span><span><span><span><span></span><span><span>x</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>x</span><span>0</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>⋅</span><span></span></span><span><span></span><span>k</span><span>,</span><span></span><span></span><span>d</span><span>y</span><span></span><span>=</span><span></span></span><span><span></span><span>(</span><span><span>a</span><span><span><span><span><span><span></span><span><span>z</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span></span><span>−</span><span></span></span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>z</span><span>0</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>)</span><span></span><span>⋅</span><span></span></span><span><span></span><span>k</span></span></span></span></span><p>其中 <span><span>ax0a_{x0}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>x</span><span>0</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span>、<span><span>az0a_{z0}</span><span><span><span></span><span><span>a</span><span><span><span><span><span><span></span><span><span><span>z</span><span>0</span></span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span></span></span></span> 是校准基准（开始控制时记录），<span><span>kk</span><span><span><span></span><span>k</span></span></span></span> 是灵敏度系数。</p><div><div><div></div><div>提示</div></div><div><p>不用陀螺仪（TYPE_ROTATION_VECTOR）而用加速度计（TYPE_ACCELEROMETER），是因为倾斜角度比角速度更直观：手机保持某个倾斜角，光标就持续匀速移动，松回水平则停止，符合「指哪打哪」的直觉。</p></div></div></section><section><h3>7.2 防抖设计<a href="#72-防抖设计"><span>#</span></a></h3><p>手持必然有抖动，两个参数直接决定手感：</p><ul>
<li><strong>死区</strong>：倾斜量小于 0.6 m/s² 时视为静止，过滤手部微抖</li>
<li><strong>灵敏度系数</strong>：倾斜 1 m/s² 对应光标移动速度，当前取值 4 倍基准灵敏度</li>
</ul><p>完整控制流程：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>进入浮动光标页</p></span></div><div><span><p>记录校准基准 ax0, az0</p></span></div><div><span><p>显示 TV 端光标</p></span></div><div><span><p>倾斜量超过死区?</p></span></div><div><span><p>按倾斜方向持续发送位移</p></span></div><div><span><p>停止移动 光标保持</p></span></div><div><span><p>点按屏幕 = 点击确认</p></span></div><div><span><p>双击或按钮 = 长按</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>进入浮动光标页</p></span></div><div><span><p>记录校准基准 ax0, az0</p></span></div><div><span><p>显示 TV 端光标</p></span></div><div><span><p>倾斜量超过死区?</p></span></div><div><span><p>按倾斜方向持续发送位移</p></span></div><div><span><p>停止移动 光标保持</p></span></div><div><span><p>点按屏幕 = 点击确认</p></span></div><div><span><p>双击或按钮 = 长按</p></span></div>
</div></div></div></section><section><h3>7.3 TV 端光标渲染<a href="#73-tv-端光标渲染"><span>#</span></a></h3><p>TV 端用 <code>TYPE_ACCESSIBILITY_OVERLAY</code> 窗口绘制光标：白色圆形主体 + 深色描边 + 中心蓝点 + 柔和光晕，直径 40px，在深浅背景上都能看清。</p><div><div><div></div><div>注意</div></div><div><p><code>TYPE_ACCESSIBILITY_OVERLAY</code> 窗口必须用 AccessibilityService 的上下文创建，用 Application Context 创建的窗口不会显示，且不会抛异常——这是最难排查的一类「静默失败」。</p></div></div></section></section>
<section><h2>8. 设计规范落地清单<a href="#8-设计规范落地清单"><span>#</span></a></h2><p>为保证四个页面视觉一致，落地时逐条对照检查：</p><ul>
<li> 全局背景 <code>#F2F2F7</code>，卡片白色、圆角 14dp</li>
<li> 图标全部线性风格，笔画粗细统一</li>
<li> 底部 Tab Bar 毛玻璃，四个入口图标 + 文字</li>
<li> 无 FAB、无 ripple、无 hamburger、无投影</li>
<li> 按键热区 ≥ 48dp</li>
<li> 电源等危险操作与常规操作区隔开</li>
<li> 页面标题与状态栏留出安全距离，不与系统状态栏重叠</li>
<li> 顶栏显示连接状态，任何页面都能确认连接是否正常</li>
</ul></section>
<section><h2>9. 踩坑与经验<a href="#9-踩坑与经验"><span>#</span></a></h2><div><div><div></div><div>警告</div></div><div><p>以下是真机调试中付出过代价的经验，做同类 App 时可以直接绕开。</p></div></div><ol>
<li>
<p><strong>光标窗口「静默失败」</strong>：Application Context 创建 overlay 窗口不报错但不显示，必须用 Service Context；<code>WindowManager.addView</code> 还必须在主线程执行，HTTP 工作线程直接调用会偶发崩溃。</p>
</li>
<li>
<p><strong>重装 APK 后无障碍服务被重置</strong>：TV 端重装后服务进入 Crashed 状态，需要完整重置序列——移除服务 → 禁用 accessibility → force-stop → 重新添加 → 启用。</p>
</li>
<li>
<p><strong>菜单键无焦点可回退</strong>：部分界面没有焦点节点，菜单指令应回退为在屏幕中心派发长按手势，否则用户会觉得「菜单键坏了」。</p>
</li>
<li>
<p><strong>状态栏与标题重叠</strong>：未处理 insets 时页面顶部内容会钻到系统状态栏底下，需统一处理状态栏安全距离。</p>
</li>
<li>
<p><strong>触控板精度</strong>：任何把「手指位置」直接映射到「大屏坐标」的方案都会死于精度不足，相对位移是唯一出路。</p>
</li>
</ol></section>
<section><h2>10. 总结<a href="#10-总结"><span>#</span></a></h2><p>设计一款遥控器 App，表面上是画界面，本质上是回答三个问题：</p><ul>
<li><strong>用户在什么姿势下用它</strong>——决定了按键布局与热区大小</li>
<li><strong>电视端能做到什么</strong>——决定了交互方案的技术边界（虚拟光标 vs 系统鼠标）</li>
<li><strong>什么样的光标移动符合直觉</strong>——决定了相对位移、死区、倾斜控制这些细节</li>
</ul><p>iOS 16 风格解决的是「看起来对」，相对位移触控板和倾斜光标解决的是「用起来对」。工具类 App 的设计没有炫技空间，把每一次点击、每一次滑动做到符合直觉，就是最好的设计。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/tcl-freeze-google/</id>
      <title type="text">TCL/雷鸟电视固件冻结 Google 相关进程的分析记录</title>
      <published>2026-08-22T00:00:00.000Z</published>
      <updated>2026-08-29T00:00:00.000Z</updated>
      <author><name>Shiyan</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/tcl-freeze-google/"/>
      <summary type="text">记录 TCL Android TV 对 Google 相关进程的冻结行为、freeze 配置的写入突破，以及 Google 三件套闪退根因的完整定位：权限问题，而非冻结。</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档记录 TCL 电视冻结 Google 相关进程的发现、确认结果、失败尝试，以及 2026-08-29 的续篇：freeze 配置写入打通与三件套闪退根因定位。<br />
<strong>适用设备：</strong> TCL <code>tcl_mt5879_cn</code>
<strong>适用系统：</strong> Android 11 / API 30，固件 V729
<strong>连接方式：</strong> ADB over Wi-Fi</p></blockquote>
<div><div><div></div><div>结论（2026-08-29 更新）</div></div><div><p>Google 三件套 APK 可以被系统识别并安装，但无法正常运行。<strong>闪退根因已定位：ADB 安装的 APK 位于 <code>/data</code>，拿不到 <code>signature|privileged</code> 级权限，进程在初始化阶段必然崩溃，与冻结机制无关</strong>（A/B 实测确认）。此前卡住的 <code>freeze</code> 配置写入也已打通，但它解决不了三件套的闪退。</p></div></div>
<hr />
<section><h2>1. 发现<a href="#1-发现"><span>#</span></a></h2><p>通过 ADB 检查 TCL 电视运行状态、系统组件和固件内容时，发现 TCL 系统存在针对 Google 进程的冻结逻辑。</p><p>相关逻辑位于：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>system_ext/apex/com.tcl.tcore.apex</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其中可以看到 <code>FreezeManager</code>、<code>TGuard</code>、冻结白名单以及 Google Play 相关字符串。TCL 系统还提供了配置入口：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>content://com.tcl.providers.config/freeze</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>2. 确认结果<a href="#2-确认结果"><span>#</span></a></h2><p><code>freeze</code> 配置是一个键值集合，完整结构如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"enable"</span><span>, </span><span>"value"</span><span>: </span><span>"true"</span><span>},</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"debug"</span><span>, </span><span>"value"</span><span>: </span><span>"false"</span><span>},</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"tcl_fastHibernate"</span><span>, </span><span>"value"</span><span>: </span><span>"true"</span><span>},</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"tcl_fastHibernateTimeout"</span><span>, </span><span>"value"</span><span>: </span><span>"1500"</span><span>},</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"tcl_fastHibernateDebug"</span><span>, </span><span>"value"</span><span>: </span><span>"false"</span><span>},</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"freezeProcessList"</span><span>, </span><span>"value"</span><span>: [</span><span>"..."</span><span>]},</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>{</span><span>"name"</span><span>: </span><span>"fastFreezeProcessList"</span><span>, </span><span>"value"</span><span>: [</span><span>"..."</span><span>]}</span></div></div><div><div><div>9</div></div><div><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>freezeProcessList</code> 中确认包含以下 Google 相关进程：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>com.google.android.gms</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>com.android.vending:download_service</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>com.android.vending:instant_app_installer</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这些进程分别涉及 Google Play services、Play Store 下载服务和即时应用安装服务。</p><div><div><div></div><div>修正（2026-08-29）</div></div><div><p>最初把 <code>freezeProcessList</code> 理解为「待冻结名单」。但名单里同时存在 <code>com.tcl.settings</code>、<code>com.android.bluetooth</code>、<code>com.google.android.tvlauncher</code> 等 TCL 自家核心应用——冻结它们电视自己就无法工作。它更可能是<strong>免冻结白名单</strong>，即名单内进程受保护。该语义尚待反编译确认，但对本文结论无影响（见第 4 节，闪退与冻结无关）。</p></div></div><p>同时确认：</p><ul>
<li>冻结实现位于 <code>tcore</code> apex 内的 <code>tcl-service.jar</code>，核心类为 <code>com.android.server.am.appfreeze.FreezeManager</code>、<code>TclFreezeConfigUpdater</code>，系统服务名为 <code>tguard</code>。</li>
<li><code>com.tcl.guard</code> 是 TCL 的守护组件，会参与进程管理。</li>
<li><code>com.tcl.providers.config</code> 是 TCL 配置提供者，不应当作为普通应用随意停用。</li>
<li>Google 三件套包可以被 <code>pm path</code> 识别，说明 APK 安装本身成功。</li>
<li>安装成功不代表获得系统签名、特权权限或 Google 认证状态——<strong>这句话正是后来定位到的闪退根因</strong>。</li>
</ul><hr /></section>
<section><h2>3. 已进行的尝试<a href="#3-已进行的尝试"><span>#</span></a></h2><section><h3>3.1 临时停用 <code>com.tcl.guard</code><a href="#31-临时停用-comtclguard"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb -s 192.168.5.2:5555 shell pm disable-user --user </span><span>0</span><span> com.tcl.guard</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果：<code>com.tcl.guard</code> 可以进入 <code>disabled-user</code> 状态，但 Google 三件套仍不能正常运行。之后已恢复：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>adb -s 192.168.5.2:5555 shell pm enable --user </span><span>0</span><span> com.tcl.guard</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3.2 安装 Google 三件套<a href="#32-安装-google-三件套"><span>#</span></a></h3><p>针对设备的 Android 11、32 位 <code>armeabi-v7a</code> 和 Android TV 环境，尝试安装：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>com.google.android.gsf</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>com.google.android.gms 26.24.34</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>com.android.vending 52.0.20</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果：三个 APK 均可被系统识别，但 Play Store 无法正常进入，Google 相关进程也不能稳定运行。</p></section><section><h3>3.3 修改 <code>freeze</code> 配置（2026-08-29 已打通）<a href="#33-修改-freeze-配置2026-08-29-已打通"><span>#</span></a></h3><p>最初普通 ADB shell 下的 JSON 写入没有成功，原始配置保持不变。</p><div><div><div></div><div>突破</div></div><div><p>失败原因是 <code>content</code> 命令的 <code>--bind</code> 参数<strong>按冒号分段解析</strong>，JSON 里的每个 <code>:</code> 都必须转义成 <code>\:</code>（Git Bash 中写作 <code>\\:</code>）。转义后写入成功：</p></div></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>J</span><span><span>=</span><span>$(</span></span><span>cat</span><span> </span><span>/data/local/tmp/freeze.json</span><span>)</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>E</span><span><span>=</span><span>$(</span></span><span>echo</span><span> </span><span>"</span><span>$J</span><span>"</span><span> | </span><span>sed</span><span> </span><span>'s/:/\\\\:/g'</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>content</span><span> </span><span>update</span><span> </span><span>--uri</span><span> </span><span>content://com.tcl.providers.config/freeze</span><span> </span><span>--bind</span><span> </span><span>"config_content:s:</span><span>$E</span><span>"</span><span> </span><span>--where</span><span> </span><span>"project_id='freeze'"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用同样的方法可以查询和回写配置（原始配置已备份）。同一技巧也解决了另一台 TCL 电视的 ADB 安装拦截问题，见《<a href="/posts/tcl-tv-adb-install-unlock/">TCL/雷鸟电视 ADB 安装拦截排查与永久放行</a>》。</p></section><section><h3>3.4 查询或调用 TCL 特殊接口<a href="#34-查询或调用-tcl-特殊接口"><span>#</span></a></h3><p>尝试使用 <code>cmd feature</code> 等接口进一步查询或修改 TCL 配置。</p><p>结果：相关功能需要更高权限或 debug 模式，普通 ADB shell 无法完成写入。</p></section><section><h3>3.5 修改系统组件或刷写固件<a href="#35-修改系统组件或刷写固件"><span>#</span></a></h3><p>未执行以下高风险操作：</p><ul>
<li>修改 <code>com.tcl.tcore.apex</code>。</li>
<li>修改 <code>FreezeManager</code> 或 <code>freezeProcessList</code>。</li>
<li>修改 <code>boot</code>、<code>system</code>、<code>vendor</code>、<code>product</code>、<code>system_ext</code> 分区。</li>
<li>刷入其他版本 TCL 固件。</li>
</ul><p>原因：这些操作涉及系统签名、分区校验和变砖风险，且没有证据表明仅修改其中一处就能恢复 Google 三件套运行。</p><hr /></section></section>
<section><h2>4. 闪退复现与根因定位（2026-08-29 续篇）<a href="#4-闪退复现与根因定位2026-08-29-续篇"><span>#</span></a></h2><section><h3>4.1 实验环境<a href="#41-实验环境"><span>#</span></a></h3><p>在另一台同固件的 TCL 电视（<code>tcl_mt5879_cn</code>，<code>192.168.5.6:5555</code>）上完成：</p><ol>
<li>使用 MindTheGapps <code>11.0.0-arm-ATV</code>（匹配 Android 11 + armeabi-v7a + Android TV）。</li>
<li>通过 ADB 安装 <code>GoogleServicesFramework.apk</code> 与 <code>PrebuiltGmsCorePano.apk</code>（即 <code>com.google.android.gms</code>），均安装成功。</li>
</ol></section><section><h3>4.2 触发方式<a href="#42-触发方式"><span>#</span></a></h3><p>国内固件<strong>开机后没有任何东西会拉起 GMS</strong>：没有 Play Store 和 setup wizard 作为触发源，且 ADB 安装的包处于 stopped 状态，收不到开机广播。需要主动拉起 GMS 的可用 Activity（如 <code>com.google.android.gms/.ads.settings.AdsSettingsActivity</code>）。</p></section><section><h3>4.3 现象：崩溃-重启死循环<a href="#43-现象崩溃-重启死循环"><span>#</span></a></h3><p>进程以<strong>前台（TOP）状态</strong>被反复拉起、随即死亡，约 300ms 一次：</p><p></p><figure><img alt="崩溃循环" loading="lazy" width="1600" height="760" src="/_astro/gms-crash-loop.C9bVZigJ_ZmnKPX.webp" /><figcaption>崩溃循环</figcaption></figure><p></p><p>注意这不是冻结——冻结只会暂停进程，不会杀进程；这里的进程死因是自身初始化抛异常。</p></section><section><h3>4.4 根因：signature|privileged 权限缺失<a href="#44-根因signatureprivileged-权限缺失"><span>#</span></a></h3><p>从 <code>dumpsys dropbox --print data_app_crash</code> 拿到完整堆栈，两个包各死于一个系统权限：</p><p></p><figure><img alt="闪退根因" loading="lazy" width="1600" height="980" src="/_astro/gms-crash-stack.BQXznAc0_2v99iF.webp" /><figcaption>闪退根因</figcaption></figure><p></p><p>GSF 的崩溃堆栈（<code>GoogleSettingsProvider</code> 初始化）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>java.lang.RuntimeException: Unable to get provider</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>com.google.android.gsf.settings.GoogleSettingsProvider:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>java.lang.SecurityException: You need MANAGE_USERS permission to: get the profile parent</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>at com.google.android.gsf.settings.GoogleSettingsProvider.onCreate(GoogleSettingsProvider.java:158)</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>at UserManager.getProfileParent(UserManager.java:3564)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>GMS 的崩溃堆栈（Application 初始化）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>java.lang.RuntimeException: Unable to create application co.g.App:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>java.lang.SecurityException:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>addOnPermissionsChangeListener: Neither user 10094 nor current process</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>has android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>MANAGE_USERS</code> 与 <code>OBSERVE_GRANT_REVOKE_PERMISSIONS</code> 都是 <code>signature|privileged</code> 级权限，只能授予 <code>/system/priv-app</code> 内、且在 privileged-permissions 白名单中的应用。ADB 安装的 APK 位于 <code>/data</code>，<strong>永远拿不到这两个权限</strong>，于是 provider / Application 初始化必然抛 <code>SecurityException</code>，系统反复拉起 → 无限闪退。</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>/system/priv-app 应用</p></span></div><div><span><p>ADB 安装的 /data 应用</p></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>拉起 GMS/GSF 界面</p></span></div><div><span><p>进程启动</p></span></div><div><span><p>初始化 ContentProvider / Application</p></span></div><div><span><p>持有 signature 级权限?</p></span></div><div><span><p>正常初始化</p></span></div><div><span><p>SecurityException</p></span></div><div><span><p>进程死亡</p></span></div><div><span><p>系统重新拉起</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>/system/priv-app 应用</p></span></div><div><span><p>ADB 安装的 /data 应用</p></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>拉起 GMS/GSF 界面</p></span></div><div><span><p>进程启动</p></span></div><div><span><p>初始化 ContentProvider / Application</p></span></div><div><span><p>持有 signature 级权限?</p></span></div><div><span><p>正常初始化</p></span></div><div><span><p>SecurityException</p></span></div><div><span><p>进程死亡</p></span></div><div><span><p>系统重新拉起</p></span></div>
</div></div></div></section><section><h3>4.5 A/B 验证：与冻结无关<a href="#45-ab-验证与冻结无关"><span>#</span></a></h3><p>控制变量实验——同一台电视、同一组 APK、只切换 <code>freeze</code> 配置：</p>

<table><thead><tr><th>freeze enable</th><th>现象</th></tr></thead><tbody><tr><td><code>true</code>（原始值）</td><td>拉起即闪退，300ms 崩溃循环</td></tr><tr><td><code>false</code>（冻结关闭）</td><td><strong>完全相同的闪退</strong>，堆栈一致</td></tr></tbody></table><p>结论：三件套的闪退与冻结机制无关，是确定性的权限缺失，ADB 层面无解。</p><hr /></section></section>
<section><h2>5. 对冻结机制的再认识<a href="#5-对冻结机制的再认识"><span>#</span></a></h2><ul>
<li><code>freeze</code> 配置可被 shell 读写（冒号转义突破后），配置修改在重启后依然生效。</li>
<li>开机时 <code>FreezeManager</code> 确实按配置工作（日志可见 <code>doUnFreeze</code>、<code>FreezeNameList: policy : freezeProcessList ... inSpecifyList : true/false</code>）。</li>
<li>但 <code>freezeProcessList</code> 的语义存疑：名单内含大量 TCL 核心应用，更接近免冻结白名单；且三件套的死亡方式（前台状态主动死亡）与冻结（暂停不杀）行为特征不符。</li>
<li>国内固件开机后不会自发拉起 GMS，「开机闪退」在这台电视上不会自发发生——只有主动打开依赖 Google 服务的界面才会触发。</li>
</ul><hr /></section>
<section><h2>6. 最终结论<a href="#6-最终结论"><span>#</span></a></h2><ol>
<li>TCL 固件确实存在进程冻结机制（<code>FreezeManager</code> / <code>tguard</code>），配置可读写，但其语义更接近白名单，且<strong>与三件套闪退无关</strong>（A/B 实测确认）。</li>
<li>三件套闪退的根因是 <code>signature|privileged</code> 级权限缺失（GSF 缺 <code>MANAGE_USERS</code>，GMS 缺 <code>OBSERVE_GRANT_REVOKE_PERMISSIONS</code>），ADB 安装的 <code>/data</code> 应用拿不到这些权限。</li>
<li>在不 root、不修改系统分区的前提下，三件套无法正常工作——修复必须将 GMS/GSF 放入 <code>/system/priv-app</code> 并加入特权权限白名单。</li>
<li>ADB 能做到的极限：安装成功（需先解除 TCL 的安装拦截，见《<a href="/posts/tcl-tv-adb-install-unlock/">TCL/雷鸟电视 ADB 安装拦截排查与永久放行</a>》）、配置接口自由读写；无法赋予系统特权。</li>
<li><code>com.tcl.guard</code> 只是守护组件之一，停用它不能改变任何结果。</li>
</ol><hr /><blockquote><p><strong>文档版本：</strong> 2026-08-29
<strong>记录状态：</strong> freeze 配置写入已打通；闪退根因已定位（权限缺失），ADB 层面无解</p></blockquote></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/e2631-research-telnet/</id>
      <title type="text">E2633转正官方版分析（Telnet篇）</title>
      <published>2026-08-04T00:00:00.000Z</published>
      <updated>2026-08-04T00:00:00.000Z</updated>
      <author><name>zlion</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/e2631-research-telnet/"/>
      <summary type="text">E2633刷入官方公版后的config.bin解密、本机信息优化、本地Web上传固件和官方系统升级研究部分。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>免责声明与使用约定</div></div><div><ol>
<li><strong>禁止以任何形式出售、打包收费、付费分发、引流获利，或将本教程及随附工具用于其他商业获利行为。</strong></li>
<li>对教程、文档或工具进行二次修改、转载和再分发时，必须保留原作者 <code>zlion</code>。</li>
<li>固件刷写、Telnet和 MTD 操作均可能造成数据丢失、设备变砖或其他不可预期后果。<strong>操作者应对自己的设备和全部操作结果负责并自行承担风险。</strong></li>
<li>如本文内容存在侵权、错误引用或其他权益问题，请联系作者核实；确认后将及时删除或修正相关内容。</li>
</ol></div></div>
<hr />
<section><h2>1. Config.bin的加解密与Telnet开启<a href="#1-configbin的加解密与telnet开启"><span>#</span></a></h2><section><h3>1.1 Config.bin的加解密<a href="#11-configbin的加解密"><span>#</span></a></h3><p><code>config.bin</code> 不是明文 XML，而是 ZTE 的 Type-4 外层容器。E2631 中使用的是 AES-256-CBC。
中兴巡天及其套娃机的值有多组，这里仅展示一组：</p>

<table><thead><tr><th>参数</th><th>值</th></tr></thead><tbody><tr><td><code>keySeed</code></td><td><code>E2631Key13515211</code></td></tr><tr><td><code>ivSeed</code></td><td><code>E2631Iv13515211</code></td></tr><tr><td><code>key</code></td><td><code>SHA256(keySeed)</code></td></tr><tr><td><code>iv</code></td><td><code>SHA256(ivSeed)</code> 取前 16 字节</td></tr><tr><td>填充</td><td>关闭，尾部补零到 AES 块边界</td></tr></tbody></table><p>解包流程如下：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>config.bin Type-4 外层容器</p></span></div><div><span><p>AES-256-CBC 解密</p></span></div><div><span><p>Type-0 内层容器</p></span></div><div><span><p>zlib 解压</p></span></div><div><span><p>最终 XML</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>config.bin Type-4 外层容器</p></span></div><div><span><p>AES-256-CBC 解密</p></span></div><div><span><p>Type-0 内层容器</p></span></div><div><span><p>zlib 解压</p></span></div><div><span><p>最终 XML</p></span></div>
</div></div></div><p>回写流程如下：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>修改后的 XML</p></span></div><div><span><p>zlib 压缩</p></span></div><div><span><p>封装 Type-0 内层容器</p></span></div><div><span><p>AES-256-CBC 加密</p></span></div><div><span><p>写回 config.bin</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>修改后的 XML</p></span></div><div><span><p>zlib 压缩</p></span></div><div><span><p>封装 Type-0 内层容器</p></span></div><div><span><p>AES-256-CBC 加密</p></span></div><div><span><p>写回 config.bin</p></span></div>
</div></div></div></section><section><h3>1.2 修改Config.bin开启Telnet<a href="#12-修改configbin开启telnet"><span>#</span></a></h3><p>这里提供一个一键开启Telnet脚本<a href="https://wwbnc.lanzoub.com/iC10a40v5r6j" target="_blank">config-telnet-enable.zip</a></p><p><code>PortControl</code> 中 TELNET 行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"ServName"</span><span> </span><span>val</span><span>=</span><span>"TELNET"</span><span>/&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"PortEnable"</span><span> </span><span>val</span><span>=</span><span>"1"</span><span>/&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>TelnetCfg</code> 中启用 LAN、关闭 WAN：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>Tbl</span><span> </span><span>name</span><span>=</span><span>"TelnetCfg"</span><span> </span><span>RowCount</span><span>=</span><span>"1"</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>&lt;</span><span>Row</span><span> </span><span>No</span><span>=</span><span>"0"</span><span>&gt;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"TS_Enable"</span><span> </span><span>val</span><span>=</span><span>"1"</span><span>/&gt;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"Wan_Enable"</span><span> </span><span>val</span><span>=</span><span>"0"</span><span>/&gt;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"Lan_Enable"</span><span> </span><span>val</span><span>=</span><span>"1"</span><span>/&gt;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"TS_Port"</span><span> </span><span>val</span><span>=</span><span>"23"</span><span>/&gt;</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"TS_UName"</span><span> </span><span>val</span><span>=</span><span>"admin"</span><span>/&gt;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>&lt;</span><span>DM</span><span> </span><span>name</span><span>=</span><span>"TS_UPwd"</span><span> </span><span>val</span><span>=</span><span>"admin"</span><span>/&gt;</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>&lt;/</span><span>Row</span><span>&gt;</span></div></div><div><div><div>10</div></div><div><span>&lt;/</span><span>Tbl</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>注意</div></div><div><p>本工具由 zlion 原创，首发于 zlion.top
禁止倒卖、打包收费或用于任何商业盈利行为，转载须保留完整出处及作者署名。</p></div></div><div><div><div></div><div>使用说明</div></div><div><ol>
<li>将 config-telnet-enable.bat、zte_config_telnet_enable.js 和 config.bin 放在同一个目录。</li>
<li>双击 config-telnet-enable.bat。</li>
<li>成功后，同目录会生成 config-telnet-enable.bin。</li>
<li>Telnet 端口为 23，账号和密码均为 admin。</li>
</ol><p>运行环境：</p><ol>
<li>完整工具包已经附带 node.exe，无需安装 Node.js 或 Python。</li>
<li>请勿单独移动 BAT；config-telnet-enable.bat、zte_config_telnet_enable.js 和 node.exe 必须放在一起。</li>
<li>不支持的配置会显示“config文件暂不适配”，且不会保留输出文件</li>
</ol></div></div></section></section>
<section><h2>2. 本机信息优化<a href="#2-本机信息优化"><span>#</span></a></h2><div><div><div></div><div>本节提醒</div></div><div><ol>
<li>适用于保留自己设备的 Tag、Wi-Fi 等身份和无线数据分区</li>
<li>适用于刷了三方固件无法绑定APP（未验证APP端是否只检测设备型号，仅猜测）</li>
</ol></div></div><p>Tag 分区与 Wi-Fi 分区性质不同，处理方式也不同：</p>

<table><thead><tr><th>分区</th><th>内容</th><th>修改方式</th></tr></thead><tbody><tr><td>Tag（mtd2）</td><td>裸结构化记录 + CRC32</td><td><code>setmac</code> 逐参数改，或 <code>mtd_write_tag</code> 整区写入（2.2）</td></tr><tr><td>Wi-Fi（mtd3）</td><td>JFFS2 文件系统，内含 <code>caldata</code></td><td>用备份文件在挂载点写回（2.3）</td></tr></tbody></table><section><h3>2.1 Tag 、Wifi 分区读取<a href="#21-tag-wifi-分区读取"><span>#</span></a></h3><p>在路由器 Telnet 终端执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/proc/mtd</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mtd2: 00100000 00020000 "tag"</span></div></div><div><div><div>2</div></div><div><span>mtd3: 00100000 00020000 "wifi"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两者大小均为 <code>0x100000</code>，即 <code>1048576</code> 字节。</p><p><strong>Tag 分区</strong>是裸结构化数据，直接读设备节点：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/dev/mtd2</span><span> &gt; </span><span>/tmp/tag-backup.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>Wi-Fi 分区</strong>（mtd3）是 JFFS2 文件系统，开机已经挂载在 <code>/wlan</code>，里面只有 <code>caldata</code> 一个文件（196,608 字节）。备份时<strong>直接读这个文件</strong> —— 恢复时原样写回即可，不需要任何解包步骤：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/wlan/caldata</span><span> &gt; </span><span>/tmp/caldata-backup.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>若 <code>/wlan</code> 没有挂载</div></div><div><p>先执行 <code>mount -t jffs2 /dev/mtdblock3 /wlan</code>，再按上面的命令备份。</p></div></div><p>确认电脑的 TFTP 服务已启动，根目录允许写入。</p><p>上传 Tag 备份：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tftp</span><span> </span><span>-p</span><span> </span><span>-l</span><span> </span><span>/tmp/tag-backup.bin</span><span> </span><span>-r</span><span> </span><span>tag-backup.bin</span><span> </span><span>192.168.5.7</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上传 Wi-Fi 校准备份：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tftp</span><span> </span><span>-p</span><span> </span><span>-l</span><span> </span><span>/tmp/caldata-backup.bin</span><span> </span><span>-r</span><span> </span><span>caldata-backup.bin</span><span> </span><span>192.168.5.7</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>192.168.5.7</code>请替换为电脑当前的局域网 IP。</p></section><section><h3>2.2 修改 Tag：setmac 命令与 mtd_write_tag 工具<a href="#22-修改-tagsetmac-命令与-mtd_write_tag-工具"><span>#</span></a></h3><p>Tag 分区（mtd2）是裸结构化数据加 CRC32 校验，Tag 中同时存在主 MAC、第二 MAC、MAC 前缀、D-SN、S/N、型号和 Wi-Fi 信息。按场景有两种修改方式：</p>

<table><thead><tr><th>方式</th><th>原理</th><th>适用场景</th></tr></thead><tbody><tr><td>方法一：<code>setmac</code> 命令（推荐）</td><td>固件自带命令逐参数修改，自动完成 NAND 擦除并重算 CRC32</td><td>改型号、MAC 等单个字段</td></tr><tr><td>方法二：<code>mtd_write_tag</code> 工具</td><td>自编译 MTD 写入工具，把改好的 Tag 整区镜像一次擦写写入</td><td>整区替换，或 <code>setmac</code> 不可用的设备</td></tr></tbody></table><section><h4>方法一：setmac 命令（推荐）<a href="#方法一setmac-命令推荐"><span>#</span></a></h4><p>设备固件自带 <code>/bin/setmac</code>，它通过 <code>libtagparam.so</code> + <code>libmtduserapi.so</code> 直接读写 Tag 分区，<strong>内部会自动完成 NAND 擦除并重算 CRC32</strong> —— 不需要离线改文件，也不需要自编译任何工具。</p><p>用法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>setmac</span><span>                        </span><span># 无参数时打印帮助</span></div></div><div><div><div>2</div></div><div><span>setmac</span><span> </span><span>show2</span><span>                  </span><span># 列出全部具名参数</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>setmac</span><span> </span><span>&lt;action</span><span> </span><span>type&gt;</span><span> </span><span>&lt;para_id&gt;</span><span> [&lt;hex </span><span>string</span><span> </span><span>para_val&gt;]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>action type</code> 含义：</p>

<table><thead><tr><th>值</th><th>动作</th></tr></thead><tbody><tr><td><code>1</code></td><td>set，写入参数</td></tr><tr><td><code>2</code></td><td>get，读取参数</td></tr><tr><td><code>3</code></td><td>del，删除参数</td></tr><tr><td><code>4</code></td><td>format，<strong>格式化整个 Tag 区（危险，勿用）</strong></td></tr></tbody></table><p>查看当前参数：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>setmac</span><span> </span><span>show2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>===============Current Status of TagParam===============</span></div></div><div><div><div>2</div></div><div><span>Mac[ID: 256] is set to AA BB CC DD EE FF</span></div></div><div><div><div>3</div></div><div><span>OUI[ID: 768] is set to AABBCC</span></div></div><div><div><div>4</div></div><div><span>DeviceSerialNumber[ID: 512] is set to XXXXXXXXXXXXXXXXX</span></div></div><div><div><div>5</div></div><div><span>WirelessNetName[ID: 1024] is set to Example-SSID</span></div></div><div><div><div>6</div></div><div><span>WlanPass[ID: 1296] is set to ExamplePass</span></div></div><div><div><div>7</div></div><div><span>...</span></div></div><div><div><div>8</div></div><div><span>==============</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常用参数 ID：</p>

<table><thead><tr><th>参数</th><th>ID</th><th>参数</th><th>ID</th></tr></thead><tbody><tr><td><code>Mac</code></td><td>256</td><td><code>WirelessNetName</code></td><td>1024</td></tr><tr><td>第二 MAC</td><td>257</td><td><code>WirelessNetName5G</code></td><td>1028</td></tr><tr><td><code>DeviceSerialNumber</code></td><td>512</td><td><code>WlanPass</code></td><td>1296</td></tr><tr><td><code>OUI</code></td><td>768</td><td><code>WlanPass5G</code></td><td>1328</td></tr><tr><td>运营商代码</td><td>1793</td><td><strong>型号</strong></td><td><strong>33024</strong></td></tr></tbody></table><div><div><div></div><div>型号不在 show2 列表里</div></div><div><p><code>setmac show2</code> 只列出它”具名”的那批参数，型号（ID <code>33024</code>）不在其中，但可以用 ID 直接读写。
所有 <code>get</code> 的返回值都是<strong>十六进制</strong>，需要自己解成 ASCII。</p></div></div><section><h5>改型号（E2633 转 E2631）<a href="#改型号e2633-转-e2631"><span>#</span></a></h5><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 读当前型号</span></div></div><div><div><div>2</div></div><div><span>setmac</span><span> </span><span>2</span><span> </span><span>33024</span></div></div><div><div><div>3</div></div><div><span>#   2633 设备预期返回：5a58484e204532363333   ( "ZXHN E2633" )</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 2. 写入 E2631</span></div></div><div><div><div>6</div></div><div><span>setmac</span><span> </span><span>1</span><span> </span><span>33024</span><span> </span><span>5A58484E204532363331</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 3. 回读验证</span></div></div><div><div><div>9</div></div><div><span>setmac</span><span> </span><span>2</span><span> </span><span>33024</span></div></div><div><div><div>10</div></div><div><span>#   预期返回：5a58484e204532363331</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>十六进制可以交给设备自己算，不用手推：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>printf</span><span> </span><span>'ZXHN E2631'</span><span> | </span><span>hexdump</span><span> </span><span>-v</span><span> </span><span>-e</span><span> </span><span>'1/1 "%02x"'</span></div></div><div><div><div>2</div></div><div><span># 5a58484e204532363331</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常见形式对照（把末尾 <code>33</code> 改成 <code>31</code>）：</p>

<table><thead><tr><th>型号字符串</th><th>hex</th></tr></thead><tbody><tr><td><code>ZXHN E2633</code></td><td><code>5A58484E204532363333</code></td></tr><tr><td><code>ZXHN E2631</code></td><td><code>5A58484E204532363331</code></td></tr><tr><td><code>ZTE E2633</code></td><td><code>5A5445204532363333</code></td></tr><tr><td><code>E2633</code></td><td><code>4532363333</code></td></tr></tbody></table><div><div><div></div><div>长度要一致</div></div><div><p>型号字段长度为 10 字节（<code>ZXHN E2631</code>）。<code>setmac</code> 对长度有校验，改型号时请与读取值保持等长，
否则可能被拒绝，或破坏后续记录结构。</p></div></div><p><strong>只改型号时，其余字段（MAC、S/N、OUI、Wi-Fi）全部保持原值</strong> —— 这正是”其他信息都还是自己贴纸上的”。若需单独修正 MAC：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>setmac</span><span> </span><span>2</span><span> </span><span>256</span><span>                       </span><span># 读当前主 MAC</span></div></div><div><div><div>2</div></div><div><span>setmac</span><span> </span><span>1</span><span> </span><span>256</span><span> </span><span>AABBCCDDEEFF</span><span>          </span><span># 主 MAC（2.4G）</span></div></div><div><div><div>3</div></div><div><span>setmac</span><span> </span><span>1</span><span> </span><span>257</span><span> </span><span>AABBCCDDEE00</span><span>          </span><span># 第二 MAC（5G）</span></div></div><div><div><div>4</div></div><div><span>setmac</span><span> </span><span>1</span><span> </span><span>768</span><span> </span><span>AABBCC</span><span>                </span><span># OUI = MAC 前三字节</span></div></div><div><div><div>5</div></div><div><span>setmac</span><span> </span><span>show2</span><span>                       </span><span># 回读确认</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>改完重启。Tag 在启动阶段被读取（<code>initwifi</code> 会调用 <code>setwlanmac</code> 把 MAC 应用到无线接口），<code>cspd</code> 与 Web 页面也会重新读取：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>reboot</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>风险提示</div></div><div><ul>
<li><code>setmac 4</code> 会<strong>格式化整个 Tag 分区</strong>，MAC、S/N、OUI、Wi-Fi 信息全部清空，<strong>不要执行</strong>。</li>
<li><code>setmac 3 &lt;id&gt;</code> 删除单个参数，同样危险。</li>
<li>改型号或 MAC 属于改变设备身份，会影响 TR-069 上报、APP 绑定与 Web 显示；操作前建议先用 <code>setmac show2</code> 记录全部原值，并顺手备份：
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>-p</span><span> </span><span>/wlan/bak</span><span> &amp;&amp; </span><span>cat</span><span> </span><span>/dev/mtd2</span><span> &gt; </span><span>/wlan/bak/tag-backup.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></div></div></section></section><section><h4>方法二：mtd_write_tag 整区写入（备选）<a href="#方法二mtd_write_tag-整区写入备选"><span>#</span></a></h4><p><code>mtd_write_tag</code> 是自己编译的 ARMv7 MTD 写入工具，<a href="https://wwbnc.lanzoub.com/iW88t40izsne" target="_blank">点此下载 mtd_write_tag_armv7</a>（<code>common-tools</code> 包内也附带，见 6.1 节）。它按 MTD 的擦除块、写入页和坏块规则执行整区擦写，适合需要整区替换（例如离线改好整个 Tag 镜像），或 <code>setmac</code> 不可用的设备。<strong>该工具只针对 Tag 的 MTD 写入流程，不能未经验证地用于 Wi-Fi、Bootloader 或其他分区。</strong></p><p>若需要离线生成改好的 Tag 镜像，可使用脚本<a href="https://wwbnc.lanzoub.com/iLgJ840kyibi" target="_blank">改型号为E2631</a>：把 <code>tag-backup.bin</code> 重命名为 <code>tag.bin</code>，与脚本放在同一级目录，双击脚本即可得到 <code>tag-E2631.bin</code>，其他信息仍是自己设备贴纸上的。</p><p></p><figure><img alt="Tag分区信息" loading="lazy" width="478" height="554" src="/_astro/tag.CdknOFPz_DHBD.webp" /><figcaption>Tag分区信息</figcaption></figure><p></p><p>Tag 结构（离线自行修改镜像时，需要自己重算 CRC32）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>0x00  33 33 33 33</span></div></div><div><div><div>2</div></div><div><span>0x04  payload size，小端</span></div></div><div><div><div>3</div></div><div><span>0x08  CRC32，小端</span></div></div><div><div><div>4</div></div><div><span>0x0c  记录区</span></div></div><div><div><div>5</div></div><div><span>尾部  ff 填充</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>CRC32 计算范围从 <code>0x0c</code> 开始，长度由 payload size 决定。</p><p>先把改好的 Tag 镜像和工具通过 TFTP 传入路由器（<code>192.168.5.7</code> 是电脑的 TFTP 地址，请按实际网络修改）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ping</span><span> </span><span>-c</span><span> </span><span>3</span><span> </span><span>192.168.5.7</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>rm</span><span> </span><span>-f</span><span> </span><span>/tmp/tag.bin</span><span> </span><span>/tmp/mtd_write_tag</span></div></div><div><div><div>4</div></div><div><span>tftp</span><span> </span><span>-g</span><span> </span><span>-l</span><span> </span><span>/tmp/tag.bin</span><span> </span><span>-r</span><span> </span><span>tag.bin</span><span> </span><span>192.168.5.7</span></div></div><div><div><div>5</div></div><div><span>tftp</span><span> </span><span>-g</span><span> </span><span>-l</span><span> </span><span>/tmp/mtd_write_tag</span><span> </span><span>-r</span><span> </span><span>mtd_write_tag_armv7</span><span> </span><span>192.168.5.7</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>上面脚本生成的是 <code>tag-E2631.bin</code>，传输时按实际文件名填写 <code>-r</code> 参数即可。</p><p>先确认程序用法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>chmod</span><span> </span><span>+x</span><span> </span><span>/tmp/mtd_write_tag</span></div></div><div><div><div>2</div></div><div><span>/tmp/mtd_write_tag</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应能看到类似用法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Usage: /tmp/mtd_write_tag --yes /dev/mtdN file.bin</span></div></div><div><div><div>2</div></div><div><span>Example: /tmp/mtd_write_tag --yes /dev/mtd2 /tmp/tag.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认输入文件为目标 Tag 分区大小后执行写入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ls</span><span> </span><span>-l</span><span> </span><span>/tmp/tag.bin</span></div></div><div><div><div>2</div></div><div><span>cat</span><span> </span><span>/sys/class/mtd/mtd2/name</span><span> 2&gt;</span><span>/dev/null</span></div></div><div><div><div>3</div></div><div><span>cat</span><span> </span><span>/sys/class/mtd/mtd2/size</span><span> 2&gt;</span><span>/dev/null</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>/tmp/mtd_write_tag</span><span> </span><span>--yes</span><span> </span><span>/dev/mtd2</span><span> </span><span>/tmp/tag.bin</span></div></div><div><div><div>6</div></div><div><span>sync</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>出现 <code>MEMUNLOCK warning: Operation not supported</code> 或 <code>fsync: Invalid argument</code> 时，不要只根据警告判断成功或失败，必须继续做读回校验。</p><p>写入后在路由器端读回并校验 MD5：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>md5sum</span><span> </span><span>/tmp/tag.bin</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>rm</span><span> </span><span>-f</span><span> </span><span>/tmp/tag-readback.bin</span></div></div><div><div><div>4</div></div><div><span>cat</span><span> </span><span>/dev/mtd2</span><span> &gt; </span><span>/tmp/tag-readback.bin</span></div></div><div><div><div>5</div></div><div><span>md5sum</span><span> </span><span>/tmp/tag-readback.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两次 MD5 一致，且程序没有报告擦除、写入或读回错误，才可认为 Tag 写入成功。若不一致，保留终端输出，不要重启设备，也不要继续写 Bootloader 或整片 NAND。</p></section></section><section><h3>2.3 恢复 Wi-Fi 分区：用 2.1 的备份文件写回<a href="#23-恢复-wi-fi-分区用-21-的备份文件写回"><span>#</span></a></h3><p>Wi-Fi 分区（mtd3）是 <strong>JFFS2 文件系统</strong>，开机时挂载在 <code>/wlan</code>，里面只有一个 <code>caldata</code> 文件（196,608 字节）。它与 Tag 性质完全不同：</p>

<table><thead><tr><th></th><th>Tag（mtd2）</th><th>Wi-Fi（mtd3）</th></tr></thead><tbody><tr><td>内容</td><td>裸结构化记录 + CRC32</td><td>JFFS2 文件系统</td></tr><tr><td>是否挂载</td><td>否</td><td><strong>是</strong>，挂在 <code>/wlan</code></td></tr><tr><td>能用 <code>setmac</code></td><td>能</td><td><strong>不能</strong></td></tr><tr><td>能用 <code>cat</code> 裸写</td><td>不可靠</td><td><strong>不行</strong></td></tr></tbody></table><p><strong>为什么不能用 <code>cat</code> 写：</strong> <code>cat 文件 &gt; /dev/mtd3</code> 会被<strong>直接拒绝</strong>，写请求根本不生效：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># cat /tmp/wifi.bin &gt; /dev/mtd3</span></div></div><div><div><div>2</div></div><div><span>/bin/sh: can't create /dev/mtd3: No child processes</span></div></div><div><div><div>3</div></div><div><span># echo $?</span></div></div><div><div><div>4</div></div><div><span>1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 E2631（192.168.5.2）上实测：完整 1 MiB 分区镜像、仅 4 字节内容、追加模式 <code>&gt;&gt;</code>，三种写法返回同一错误；先 <code>umount /wlan</code> 再写也照样失败；写入前后 <code>cat /dev/mtd3 | md5sum</code> 完全相同，分区内容零变化。权限与 MTD 标志均正常（<code>/dev/mtd3</code> 为 <code>crw-r--r--</code>，<code>/sys/class/mtd/mtd3/flags</code> = <code>0x400</code>，即 <code>MTD_WRITEABLE</code>）。</p><p><strong>所以”把自己那份原样写回、MD5 一致”是个假象</strong>：写操作从未生效，读回的又是分区原有内容，两者当然一致 —— <strong>MD5 相同完全不能证明写入成功。</strong></p><div><div><div></div><div>NAND 的另一个坑</div></div><div><p>E2631 用的是 SPI NAND，编程时<strong>只能把位从 <code>1</code> 变成 <code>0</code></strong>，要把位从 <code>0</code> 变回 <code>1</code> 必须先整块擦除（128 KiB）。这是裸写普遍不可靠的根本原因；不过在这台设备上，<code>cat</code> 连提交写入请求这一步都过不去。</p></div></div><p><strong>为什么不能用 <code>setmac</code>：</strong> 它依赖 <code>libtagparam.so</code>，只认 Tag 分区。</p><p><strong>正确做法：恢复挂载点里的 <code>caldata</code></strong>，擦除、ECC、坏块跳过、磨损均衡全部交给 JFFS2。</p><p>先确认挂载状态：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mount</span><span> | </span><span>grep</span><span> </span><span>wlan</span></div></div><div><div><div>2</div></div><div><span>ls</span><span> </span><span>-l</span><span> </span><span>/wlan/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>正常应看到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/dev/mtdblock3 on /wlan type jffs2 (rw,relatime)</span></div></div><div><div><div>2</div></div><div><span>----------    1 Z500_w9b root        196608 Jan  1  1970 caldata</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>把 2.1 备份的 <code>caldata-backup.bin</code> 传回设备：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tftp</span><span> </span><span>-g</span><span> </span><span>-l</span><span> </span><span>/tmp/caldata</span><span> </span><span>-r</span><span> </span><span>caldata-backup.bin</span><span> </span><span>192.168.5.7</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>写回挂载点：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cp</span><span> </span><span>/tmp/caldata</span><span> </span><span>/wlan/caldata</span></div></div><div><div><div>2</div></div><div><span>sync</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>md5sum</span><span> </span><span>/tmp/caldata</span><span> </span><span>/wlan/caldata</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两个 MD5 必须一致。随后让驱动重新加载校准数据：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>rmmod</span><span> </span><span>mt7916</span></div></div><div><div><div>2</div></div><div><span>insmod</span><span> </span><span>/lib/modules/mt7916.ko</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>mt7916</code> 通常被 <code>tm</code>、<code>plat_zxylzb_9128S</code> 等模块引用，<code>rmmod</code> 会直接失败，此时 <code>reboot</code> 即可。</p><div><div><div></div><div>不要裸写 <code>/dev/mtd3</code></div></div><div><p>Wi-Fi 分区是<strong>已经挂载的活动 JFFS2 文件系统</strong>。对它裸写不只是”写不进去”：一旦部分块真的被写坏，JFFS2 的节点头与校验会错乱，可能导致<strong>下次挂载失败、整个分区读不出来</strong> —— 比 Tag 写坏更难恢复。</p></div></div><hr /></section></section>
<section><h2>3. Web 本地上传分析研究<a href="#3-web-本地上传分析研究"><span>#</span></a></h2><div><div><div></div><div>注意（2026/08/29更新）</div></div><div><p>中兴官方系统自带Web本地上传升级界面：<a href="http://192.168.5.1/supgrade.html" target="_blank">http://192.168.5.1/supgrade.html</a>
由<a href="https://www.right.com.cn/forum/space-uid-639610.html" target="_blank">@cnjn</a>研究发现，以下内容仅作研究分享。</p></div></div><section><h3>3.1 接口和表单<a href="#31-接口和表单"><span>#</span></a></h3><p>抓到的 Web 流程：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>GET  /?_type=vueData&amp;_tag=vue_nowversion_lua</span></div></div><div><div><div>2</div></div><div><span>POST /?_type=vueData&amp;_tag=prepare_firmware_upgrade</span></div></div><div><div><div>3</div></div><div><span>POST /?_type=vueData&amp;_tag=do_firmware_upgrade</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>multipart 字段名：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>VersionUpload</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>核心表单头：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Content-Type: multipart/form-data; boundary=...</span></div></div><div><div><div>2</div></div><div><span>Content-Disposition: form-data; name="VersionUpload"; filename="firmware.bin"</span></div></div><div><div><div>3</div></div><div><span>Content-Type: application/octet-stream</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3.2 SessionTimeout 的定位<a href="#32-sessiontimeout-的定位"><span>#</span></a></h3><p>页面返回过 <code>logintoken</code>，响应头也返回过 <code>X_XSRF_TOKEN</code>。曾出现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HTTP 200</span></div></div><div><div><div>2</div></div><div><span>IF_ERRORSTR = SessionTimeout</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>以及多种 token 组合均失败：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>prepare same token  =&gt; SessionTimeout</span></div></div><div><div><div>2</div></div><div><span>prepare sessOnly    =&gt; SessionTimeout</span></div></div><div><div><div>3</div></div><div><span>prepare sessionOnly =&gt; SessionTimeout</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>后续在同一登录 Session 内获取页面 token、Cookie 和 XSRF token，并先调用 <code>vue_loadprevent_data</code>，曾得到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>prepare same:&lt;page token&gt; =&gt; SUCC</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这只说明准备阶段通过，不说明上传和刷写一定成功。</p></section><section><h3>3.3 升级管理器调试<a href="#33-升级管理器调试"><span>#</span></a></h3><p>Telnet 下可以打开升级管理器日志并查看状态：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sendcmd</span><span> </span><span>cspd.cspd.upgrade_mgr</span><span> </span><span>setDebug</span><span> </span><span>1</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>sendcmd</span><span> </span><span>cspd.cspd.upgrade_mgr</span><span> </span><span>show</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>sendcmd</span><span> </span><span>cspd.cspd.upgrade_mgr</span><span> </span><span>-p</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>sendcmd</span><span> </span><span>cspd.cspd.upgrade_mgr</span><span> </span><span>-l</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>show</code> 中的 <code>State</code>、<code>Location</code>、<code>TempDirName</code>、<code>FlashingPid</code> 和 <code>MediaPid</code> 可帮助判断升级是否真正进入后端。研究中 Web 失败后常见：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>State       :0</span></div></div><div><div><div>2</div></div><div><span>Location    :</span></div></div><div><div><div>3</div></div><div><span>TempDirName :</span></div></div><div><div><div>4</div></div><div><span>MediaPid    :</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同时没有 <code>/var/tmp/fw.bin</code>，这更像请求未进入后端刷写，而不是固件已经写入。</p></section><section><h3>3.4 请求体上限实验<a href="#34-请求体上限实验"><span>#</span></a></h3>

<table><thead><tr><th>测试文件</th><th>现象</th><th>判断</th></tr></thead><tbody><tr><td>约 300 KiB</td><td>100% 后 HTTP 200，<code>SessionTimeout</code></td><td>能传完整请求，会话仍有问题，不进入后续校验升级流程</td></tr><tr><td>约 320 KiB</td><td>100% 后连接重置</td><td>接近/超过请求体限制</td></tr><tr><td>约 330 KiB</td><td>约 97% 后重置</td><td>multipart 边界也占空间</td></tr><tr><td>约 512 KiB</td><td>约 62% 后重置</td><td>设备端提前关闭连接</td></tr><tr><td>22,413,844 bytes 官方包</td><td>无法稳定上传</td><td>不适合该页面</td></tr></tbody></table><p></p><figure><img alt="300Kib测试" loading="lazy" width="2559" height="1580" src="/_astro/300.DP0opm6j_Cy0q6.webp" /><figcaption>300Kib测试</figcaption></figure>
<figure><img alt="320Kib测试" loading="lazy" width="2559" height="1477" src="/_astro/330.CYCiqjT3_Z18SAKS.webp" /><figcaption>320Kib测试</figcaption></figure><p></p><p><code>/bin/httpd</code> 中发现：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>content length is beyond limit.</span></div></div><div><div><div>2</div></div><div><span>Http request body read fail.</span></div></div><div><div><div>3</div></div><div><span>Upload file fail for not enough space, g_nContentLength(%d) &gt;= g_nAllowedSpace(%d)</span></div></div><div><div><div>4</div></div><div><span>fwrite fail when upload.</span></div></div><div><div><div>5</div></div><div><span>VersionUpload</span></div></div><div><div><div>6</div></div><div><span>/var/tmp/fw.bin</span></div></div><div><div><div>7</div></div><div><span>do_firmware_upgrade</span></div></div><div><div><div>8</div></div><div><span>my_upload_file</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这证明有 body length、临时空间和写文件分支；字符串本身不能证明唯一的上限数值。</p></section><section><h3>3.5 <code>/tmp</code> 空间与 Web 限制<a href="#35-tmp-空间与-web-限制"><span>#</span></a></h3><p>当时读取到：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/var/tmp 约 40960 KiB，可用约 40736 KiB</span></div></div><div><div><div>2</div></div><div><span>/tmp     约 20480 KiB，可用约 19456 KiB</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>官方包约 21.38 MiB，在 <code>/var/tmp</code> 理论上放得下，但仍被 HTTP 处理路径重置。磁盘空间够，不等于请求体限制够，也不等于升级进程会接受该文件。</p></section><section><h3>3.6 Wireshark 过滤器<a href="#36-wireshark-过滤器"><span>#</span></a></h3><p>只看 HTTP 上传：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ip.addr == 192.168.5.1 &amp;&amp; ip.addr == 192.168.5.7 &amp;&amp; tcp.port == 80</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只看电脑到路由器：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ip.src == 192.168.5.7 &amp;&amp; ip.dst == 192.168.5.1 &amp;&amp; tcp.dstport == 80</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只看重置/重传：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>tcp.flags.reset == 1 || tcp.analysis.retransmission</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>早期过滤 <code>udp</code> 只能看到 DNS、多播或调试包，不能证明 HTTP 升级。抓包中由 <code>192.168.5.1:80</code> 返回 <code>RST, ACK</code>，与设备端连接关闭相符。</p></section><section><h3>3.7 Web 结论<a href="#37-web-结论"><span>#</span></a></h3><ul>
<li>进度 100% 只表示浏览器发完请求，不表示设备已经校验或写入。</li>
<li><code>SessionTimeout</code> 和 <code>ERR_CONNECTION_RESET</code> 必须分层排查。</li>
<li>大于几百 KiB 的正式升级包应走 Telnet + TFTP + 本地 <code>fw_flashing</code>，不应继续反复提交 Web 请求。</li>
<li>隐藏页面是真实刷写路径，不是安全测试页面。</li>
</ul><hr /></section></section>
<section><h2>4. fw-flashing 与双槽启动<a href="#4-fw-flashing-与双槽启动"><span>#</span></a></h2><section><h3>4.1 参数和最小失败测试<a href="#41-参数和最小失败测试"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>/bin/fw_flashing</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>/bin/fw_flashing</span><span> </span><span>-h</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出包含：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fw_flashing:no parameter</span></div></div><div><div><div>2</div></div><div><span>d:r:p:f:m:v:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认 <code>-f</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>test</span><span> &gt; </span><span>/var/tmp/fw-test.bin</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>/bin/fw_flashing</span><span> </span><span>-f</span><span> </span><span>/var/tmp/fw-test.bin</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>echo</span><span> </span><span>$?</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>fw_flashing name=/var/tmp/fw-test.bin,states.st_size =5</span></div></div><div><div><div>2</div></div><div><span>read magic failed!</span></div></div><div><div><div>3</div></div><div><span>GetVersionInfo error==========fw_flashing error==========</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>说明 <code>-f</code> 确实接受路径，并要求带固件 Magic/版本头。</p></section><section><h3>4.2 官方包成功刷写<a href="#42-官方包成功刷写"><span>#</span></a></h3><p>电脑端确认：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>dir</span><span> fw.bin</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>certutil</span><span> -hashfile fw.bin MD5</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>路由器获取并校验：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/var/tmp</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>rm</span><span> </span><span>-f</span><span> </span><span>/var/tmp/fw.bin</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>tftp</span><span> </span><span>-g</span><span> </span><span>-l</span><span> </span><span>/var/tmp/fw.bin</span><span> </span><span>-r</span><span> </span><span>fw.bin</span><span> </span><span>192.168.5.7</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>ls</span><span> </span><span>-l</span><span> </span><span>/var/tmp/fw.bin</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>md5sum</span><span> </span><span>/var/tmp/fw.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>本次包 MD5：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>de682a3dce2b128cf7edeb8ac5f958a1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>/bin/fw_flashing</span><span> </span><span>-f</span><span> </span><span>/var/tmp/fw.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>关键输出：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>RunningVerStartAddr:700000</span></div></div><div><div><div>2</div></div><div><span>RunningHeadHighAddr:2300000</span></div></div><div><div><div>3</div></div><div><span>g_ver_towrite=2</span></div></div><div><div><div>4</div></div><div><span>g_upgrade_startaddr=0x2300000</span></div></div><div><div><div>5</div></div><div><span>g_upgrade_endaddr=0x3f00000</span></div></div><div><div><div>6</div></div><div><span>firmware_size=22413312</span></div></div><div><div><div>7</div></div><div><span>firmware_offset=0x214</span></div></div><div><div><div>8</div></div><div><span>sect_size=0x20000</span></div></div><div><div><div>9</div></div><div><span>WriteVersion head success!</span></div></div><div><div><div>10</div></div><div><span>==========fw_flashing success==================</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>说明程序选择备用高槽、擦除并写入了包体，同时更新了版本头。</p></section><section><h3>4.3 确认当前槽<a href="#43-确认当前槽"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/proc/cmdline</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>cat</span><span> </span><span>/proc/zte/verinfo/versionstates</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>cat</span><span> </span><span>/proc/zte/verinfo/softVersion</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>cat</span><span> </span><span>/proc/zte/verinfo/othersoftVersion</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>槽位关键字段：</p>

<table><thead><tr><th>字段</th><th>含义</th></tr></thead><tbody><tr><td><code>currentverphyaddr</code></td><td>当前运行物理起始地址</td></tr><tr><td><code>backverphyaddr</code></td><td>备用槽物理起始地址</td></tr><tr><td><code>curverIsBad</code></td><td>当前槽是否标坏</td></tr><tr><td><code>backverIsBad</code></td><td>备用槽是否标坏</td></tr><tr><td><code>curverSerialNumber</code></td><td>当前记录序号</td></tr><tr><td><code>othersoftVersion</code></td><td>备用槽版本记录</td></tr></tbody></table><p>本次低槽运行时确认：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>currentverphyaddr: 0x00700000</span></div></div><div><div><div>2</div></div><div><span>backverphyaddr:    0x02300000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>另一次从高槽启动时，<code>/proc/cmdline</code> 尾部出现 <code>0x2300000</code>。两者一致时，才是较强的槽位证据；不能只看 <code>root=/dev/mtdblock8</code>。</p></section><section><h3>4.4 已确认的检查方向<a href="#44-已确认的检查方向"><span>#</span></a></h3><p>从 <code>/bin/fw_flashing</code> 字符串、符号和运行输出来看，存在：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>read magic failed!</span></div></div><div><div><div>2</div></div><div><span>GetVersionInfo</span></div></div><div><div><div>3</div></div><div><span>check_ver_board</span></div></div><div><div><div>4</div></div><div><span>invalid vid version!</span></div></div><div><div><div>5</div></div><div><span>CheckBootFile</span></div></div><div><div><div>6</div></div><div><span>CheckFile</span></div></div><div><div><div>7</div></div><div><span>Checkheader</span></div></div><div><div><div>8</div></div><div><span>CspUserMtdRead / Write / Erase</span></div></div><div><div><div>9</div></div><div><span>flash addr overflow</span></div></div><div><div><div>10</div></div><div><span>uImage</span></div></div><div><div><div>11</div></div><div><span>CRC</span></div></div><div><div><div>12</div></div><div><span>CspSwitchVersion</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>合理的校验链：</p><ol>
<li>文件大小和可读性。</li>
<li>Magic、版本和升级头。</li>
<li>板型、VID、版本关系。</li>
<li>长度和目标槽范围。</li>
<li>uImage 头部/数据 CRC，具体分支由版本决定。</li>
<li>擦除、写入和写后读回。</li>
<li>写版本头、切换或等待下一次启动选择。</li>
</ol><p>未找到明确 RSA 公钥验证调用。RSA 可能在 <code>cspd</code>、Web、BootROM 或其他库中，不能仅凭 <code>fw_flashing</code> 字符串判断。</p></section></section>
<section><h2>5. 试错部分<a href="#5-试错部分"><span>#</span></a></h2><section><h3>5.1 Tag 直接写失败<a href="#51-tag-直接写失败"><span>#</span></a></h3><p>尝试 <code>cat</code>、<code>mknod</code>、<code>chmod</code> 后仍 MD5 不一致。设备没有 <code>mtd_debug</code>、<code>flash_erase</code>、<code>nandwrite</code>，只有 <code>fw_flashing</code>、<code>upgradetest</code> 等程序；这些程序没有暴露 Tag 写入接口。</p><p>最终是自己编译 MTD ioctl 工具（<code>mtd_write_tag</code>）完成写入的。</p><p>后来才发现，官方固件本身就自带 <code>/bin/setmac</code>（见 2.2 节），它通过 <code>libtagparam.so</code> + <code>libmtduserapi.so</code> 自动完成擦除并重算 CRC32 —— <strong>改 Tag 用一条命令就够了，不需要自编译任何工具</strong>。<code>mtd_write_tag</code> 仍然可用，适合需要整区替换或 <code>setmac</code> 不可用的场景。</p></section><section><h3>5.2 Web 反复失败<a href="#52-web-反复失败"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>升级准备失败：SessionTimeout</span></div></div><div><div><div>2</div></div><div><span>上传连接失败</span></div></div><div><div><div>3</div></div><div><span>net::ERR_CONNECTION_RESET</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>进度 31%、62%、97%、100% 后都见过失败。用小文件先拆分问题，确认大文件主要被 HTTP 路径限制后，停止继续提交 22 MiB 包，转为本地 <code>fw_flashing</code>。</p></section><section><h3>5.3 未验证自动回退<a href="#53-未验证自动回退"><span>#</span></a></h3><p>已确认 <code>currentverphyaddr</code>、<code>backverphyaddr</code>、两个 <code>IsBad</code> 字段，但没有故意刷坏包测试回退。不要为了得到一个“确定答案”而破坏当前可启动槽。</p><hr /></section></section>
<section><h2>6. 通用脚本（自行研究使用）<a href="#6-通用脚本自行研究使用"><span>#</span></a></h2><section><h3>6.1 目录<a href="#61-目录"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>common-tools/</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>README.md</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>zte_type4_config_tool.js</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>enable_telnet_admin.bat</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>zte_firmware_analyzer.js</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>fw_slot_status.sh</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>mtd_write_tag_armv7</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="https://wwbnc.lanzoub.com/i5JFl40o809g" target="_blank">点此下载</a></p></section><section><h3>6.2 Type-4 配置工具<a href="#62-type-4-配置工具"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>node</span><span> </span><span>common-tools/zte_type4_config_tool.js</span><span> </span><span>inspect</span><span> </span><span>config.bin</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>node</span><span> </span><span>common-tools/zte_type4_config_tool.js</span><span> </span><span>decrypt</span><span> </span><span>config.bin</span><span> </span><span>config.xml</span><span> </span><span>--profile</span><span> </span><span>e2631</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>node</span><span> </span><span>common-tools/zte_type4_config_tool.js</span><span> </span><span>encrypt</span><span> </span><span>config.xml</span><span> </span><span>config-new.bin</span><span> </span><span>--profile</span><span> </span><span>e2631</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>node</span><span> </span><span>common-tools/zte_type4_config_tool.js</span><span> </span><span>enable-telnet</span><span> </span><span>config.bin</span><span> </span><span>config-telnet.bin</span><span> </span><span>--profile</span><span> </span><span>e2631</span><span> </span><span>--user</span><span> </span><span>admin</span><span> </span><span>--password</span><span> </span><span>admin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>其他机型必须显式提供已验证种子：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>node</span><span> </span><span>common-tools/zte_type4_config_tool.js</span><span> </span><span>decrypt</span><span> </span><span>config.bin</span><span> </span><span>config.xml</span><span> </span><span>--key-seed</span><span> </span><span>MODELKeyXXXXXXXX</span><span> </span><span>--iv-seed</span><span> </span><span>MODELIvXXXXXXXX</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>6.3 固件分析工具<a href="#63-固件分析工具"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>node</span><span> </span><span>common-tools/zte_firmware_analyzer.js</span><span> </span><span>firmware.bin</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>node</span><span> </span><span>common-tools/zte_firmware_analyzer.js</span><span> </span><span>firmware.bin</span><span> </span><span>--signature-offset</span><span> </span><span>0x37fdcc</span><span> </span><span>--signature-size</span><span> </span><span>564</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>输出哈希、可打印头部、型号标记、uImage CRC 和指定签名区零字节数。签名区非零不等于签名有效。</p><hr /></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/skyworth-adb-web/</id>
      <title type="text">创维盒子ADB动态密码在线计算器</title>
      <published>2026-07-30T00:00:00.000Z</published>
      <updated>2026-07-30T00:00:00.000Z</updated>
      <author><name>Shiyan</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/skyworth-adb-web/"/>
      <summary type="text">介绍创维盒子ADB动态密码的生成算法，和一个在线计算网页。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>免责说明</div></div><div><p>本工具及算法仅供技术学习与合法设备调试使用。请勿用于非法破解或侵犯他人设备隐私，使用者需自行承担相关法律责任。</p></div></div>
<section><h2>说明<a href="#说明"><span>#</span></a></h2><ol>
<li>创维盒子开启ADB调试时，部分型号会要求输入<strong>动态密码</strong>，该密码由MAC地址和屏幕显示的随机码共同计算得出。</li>
<li>本文公开的算法来源于开源项目，并搭建了在线计算页面，方便快速得到结果。</li>
</ol></section>
<section><h2>在线工具地址<a href="#在线工具地址"><span>#</span></a></h2><p>🔗 <strong>在线计算页面</strong>： <a href="https://skyworth-adb.zlion.top/" target="_blank">https://skyworth-adb.zlion.top/</a></p><p>网页UI借助AI工具生成，算法核心来自开源项目 <a href="https://github.com/wiver53/skyworth-ADB-" target="_blank">wiver53/skyworth-ADB</a>。</p></section>
<section><h2>动态密码算法详解<a href="#动态密码算法详解"><span>#</span></a></h2><p>动态密码的生成共分三步，下文以具体示例说明。</p><section><h3>第一步：拼接原始字符串<a href="#第一步拼接原始字符串"><span>#</span></a></h3><p>格式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>JSCMCC_SKYWORTH + [MAC 地址] + [随机码] + tianhuaxin@skyworth.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>假设设备信息如下：</p><ul>
<li>MAC 地址：<code>A1:B2:C3:D4:E5:F6</code></li>
<li>随机码：<code>123456</code></li>
</ul><p>拼接后的原始字符串为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>JSCMCC_SKYWORTHA1:B2:C3:D4:E5:F6123456tianhuaxin@skyworth.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>第二步：计算 MD5 哈希<a href="#第二步计算-md5-哈希"><span>#</span></a></h3><p>对上述完整字符串进行 MD5 运算，得到 32 位十六进制摘要。</p><p>例如上一步字符串的 MD5 结果：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>e10adc3949ba59abbe56e057f20f883e</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>以上结果仅为示例，非真实计算结果。</p></blockquote></section><section><h3>第三步：计算最终动态密码<a href="#第三步计算最终动态密码"><span>#</span></a></h3><p>将 32 位 MD5 字符串从中间一分为二，分别求和后相乘。</p><p>前半段：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>e10adc3949ba59ab</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>后半段：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>be56e057f20f883e</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>将每段中的十六进制字符转换为十进制数字后求和：</p><ul>
<li>字符 <code>3</code> → <code>3</code></li>
<li>字符 <code>a</code> → <code>10</code></li>
<li>字符 <code>f</code> → <code>15</code></li>
</ul><p>得到两个总和后，将两者相乘，乘积即为最终的动态密码。</p></section><section><h3>本地计算<a href="#本地计算"><span>#</span></a></h3><p>如果不想使用在线页面，也可以通过命令行计算。以下示例适用于 Linux/macOS：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 拼接字符串，请替换为实际的 MAC 地址和随机码</span></div></div><div><div><div>2</div></div><div><span>STR</span><span>=</span><span>"JSCMCC_SKYWORTHA1:B2:C3:D4:E5:F6123456tianhuaxin@skyworth.com"</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 2. 计算 MD5</span></div></div><div><div><div>5</div></div><div><span>MD5</span><span><span>=</span><span>$(</span></span><span>echo</span><span> </span><span>-n</span><span> </span><span>"</span><span>$STR</span><span>"</span><span> | </span><span>md5sum</span><span> | </span><span>cut</span><span> </span><span>-d</span><span>' '</span><span> </span><span>-f1</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 3. 分割并求和相乘</span></div></div><div><div><div>8</div></div><div><span>python3</span><span> </span><span>-c</span><span> </span><span>"</span></div></div><div><div><div>9</div></div><div><span>md5='</span><span>$MD5</span><span>'</span></div></div><div><div><div>10</div></div><div><span>half=len(md5)//2</span></div></div><div><div><div>11</div></div><div><span>s1=sum(int(c,16) for c in md5[:half])</span></div></div><div><div><div>12</div></div><div><span>s2=sum(int(c,16) for c in md5[half:])</span></div></div><div><div><div>13</div></div><div><span>print(s1*s2)</span></div></div><div><div><div>14</div></div><div><span>"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>常见问题<a href="#常见问题"><span>#</span></a></h2><section><h3>Q：为什么我的盒子输入密码后提示错误？<a href="#q为什么我的盒子输入密码后提示错误"><span>#</span></a></h3><p>请检查 MAC 地址和随机码是否与屏幕显示完全一致，尤其注意：</p><ul>
<li>MAC 地址的大小写</li>
<li>MAC 地址中的冒号格式</li>
<li>随机码是否为最新显示的 6 位数字</li>
</ul></section><section><h3>Q：所有创维盒子都适用吗？<a href="#q所有创维盒子都适用吗"><span>#</span></a></h3><p>该算法主要适用于国内运营商定制版，例如江苏移动创维盒子。其他型号可能存在差异，请以实际测试结果为准。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/e2633-research-ttl/</id>
      <title type="text">E2633刷入中兴官方AX3000 版本（TTL篇）</title>
      <published>2026-07-20T00:00:00.000Z</published>
      <updated>2026-08-17T00:00:00.000Z</updated>
      <author><name>zlion</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/e2633-research-ttl/"/>
      <summary type="text">记录 E2633通过 TTL 进入 U-Boot 后，使用 Nand Write 官方巡天 AX3000 版本固件的流程。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>免责声明与使用约定</div></div><div><ol>
<li>固件刷写、分区读写、Telnet、TTL、U-Boot 和 MTD 操作均可能造成数据丢失、设备变砖、网络中断、保修失效或其他不可预期后果。<strong>请对自己的设备和全部操作结果负责并自行承担风险。</strong></li>
<li>教程中涉及的固件、商标、产品名称及第三方程序，其权利归各自权利人所有。如本文内容存在侵权、错误引用或其他权益问题，请联系作者核实；确认后将及时删除或修正相关内容。</li>
</ol></div></div>
<section><h2>说明<a href="#说明"><span>#</span></a></h2><ol>
<li>由于是全分区整片写入，刷完后默认 WiFi 名称会变为 <code>ZTE-Q2QHKu</code>，重置一遍后密码为 <code>bhdh3954</code>。</li>
<li>所有内容仅适用于 256 MB RAM ➕ 128 MB ROM 版本。</li>
<li>本教程会覆盖你的所有分区，无法找回，若想保留，请先参考<a href="https://blog.zlion.top/posts/e2631-research-telnet/#2-%E6%9C%AC%E6%9C%BA%E4%BF%A1%E6%81%AF%E4%BC%98%E5%8C%96">Telnet篇</a>备份两个关键分区：tag、wifi（tag包含SN/MAC等信息是远程控制的关键，WiFi包含本机独一无二的无线校准数据，直接覆盖可能信号不稳定）。通过Telnet还原这两个分区后再重置一遍，所有信息就变成贴纸上的了，并且默认无密码。</li>
<li>若在TFTP传输中失败，可能是传输速度太慢，超出了MobaXterm未注册版本的开启时长限制，此问题自行解决。</li>
<li>鉴于部分设备有坏块的情况，会在写入128MB镜像时报错导致变砖，现在更新一下镜像，原128MB的镜像中末尾有大约13MB都是无效数据（FF），我裁切了10MB，现在镜像只有118MB了，最多容许你的设备有80个坏块（已经非常多了，正常机子几乎不可能有这么多坏块，可以在u-boot中输入nand bad 查看）</li>
</ol></section>
<section><h2>准备工作<a href="#准备工作"><span>#</span></a></h2><ul>
<li>5元的USB 转 TTL 模块 CH340G 一个，最好带杜邦线，方便连接。</li>
<li>由编程器固件去掉 OOB 、并裁切10MB空间预留给坏块写入跳过的 <a href="https://wwbnc.lanzoub.com/iBlN541u3rxe" target="_blank">118 MB 镜像</a>。编程器固件来自 <a href="https://www.right.com.cn/forum/thread-8472425-1-1.html" target="_blank">hzw521</a>。</li>
<li>MobaXterm 等串口连接工具。</li>
<li>TFTP 服务工具（MobaXterm自带）。</li>
<li>网线一根。</li>
</ul></section>
<section><h2>操作步骤<a href="#操作步骤"><span>#</span></a></h2><section><h3>1. 拆机<a href="#1-拆机"><span>#</span></a></h3></section><section><h3>2. 连接 TTL 和网线<a href="#2-连接-ttl-和网线"><span>#</span></a></h3><p>使用网线连接电脑和路由器，参考下图连接 TTL：</p><p></p><figure><img alt="TTL 接线参考" loading="lazy" width="1095" height="571" src="/_astro/TTL.DCdDpJwJ_dKU7D.webp" /><figcaption>TTL 接线参考</figcaption></figure><p></p></section><section><h3>3. 进入 U-Boot<a href="#3-进入-u-boot"><span>#</span></a></h3><p>连接好后，使用 MobaXterm 软件进行串口连接，波特率设置为 <code>115200</code>：</p><p></p><figure><img alt="设置配置参考" loading="lazy" width="1433" height="365" src="/_astro/MXSetting.Ci8N9ptO_Z2m5lXJ.webp" /><figcaption>设置配置参考</figcaption></figure><p></p><p>路由器上电后马上按 <code>1</code> 中断系统启动，输入密码进入 U-Boot（密码来源见<a href="https://voz.vn/t/unlock-mesh-zte-e1600-2603-2615-thanh-phien-ban-quoc-te.903294/" target="_blank">论坛</a>）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>5cE080@fyBD</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4. 配置电脑网络和 TFTP<a href="#4-配置电脑网络和-tftp"><span>#</span></a></h3><p>关闭防火墙，将电脑 IPv4 改为手动配置：</p><ul>
<li>IP 地址：<code>192.168.10.1</code></li>
<li>子网掩码：<code>255.255.255.0</code></li>
</ul><p>设置固件存放目录。此时先不要开启 TFTP 服务。</p><p></p><figure><img alt="TFTP 目录配置参考" loading="lazy" width="1363" height="1016" src="/_astro/TFTP.Dore2Zdg_8bPMI.webp" /><figcaption>TFTP 目录配置参考</figcaption></figure><p></p></section><section><h3>5. 在串口终端设置 IP<a href="#5-在串口终端设置-ip"><span>#</span></a></h3><p>在 u-boot 串口终端中输入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>setenv</span><span> </span><span>ipaddr</span><span> </span><span>192.168.10.2</span></div></div><div><div><div>2</div></div><div><span>setenv</span><span> </span><span>serverip</span><span> </span><span>192.168.10.1</span></div></div><div><div><div>3</div></div><div><span>ping</span><span> </span><span>192.168.10.1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>看到类似下面的提示即表示通信正常：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>host 192.168.10.1 is alive</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时再启动前面配置好的 TFTP 服务。</p></section><section><h3>6. 通过 TFTP 传输并刷写固件<a href="#6-通过-tftp-传输并刷写固件"><span>#</span></a></h3><p>在串口终端中通过 TFTP 将固件传到 RAM：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tftp</span><span> </span><span>0x43000000</span><span> </span><span>e2631_no_oob_trim_tail_ff_10MiB.bin</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>传输完成后，确认下载大小必须为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Bytes transferred = 123731968 (7600000 hex)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认无误后，擦除整片 NAND：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nand</span><span> </span><span>erase</span><span> </span><span>0x0</span><span> </span><span>0x8000000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应当看到以下输出</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>NAND erase: device 0 whole chip</span></div></div><div><div><div>2</div></div><div><span>Erasing at 0x7fe0000 -- 100% complete.</span></div></div><div><div><div>3</div></div><div><span>OK</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>然后一次性写入：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nand</span><span> </span><span>write</span><span> </span><span>0x43000000</span><span> </span><span>0x0</span><span> </span><span>0x7600000</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>应当看到以下输出</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>NAND write: device 0 offset 0x0, size 0x7600000</span></div></div><div><div><div>2</div></div><div><span><span> </span></span><span>123731968 bytes written: OK</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>7. 重启<a href="#7-重启"><span>#</span></a></h3><p>刷写完成后执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>reset</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时还可以在串口终端中看到系统启动的日志：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Boot SPI NAND</span></div></div><div><div><div>2</div></div><div><span>start read bootheader</span></div></div><div><div><div>3</div></div><div><span>start read secondboot</span></div></div><div><div><div>4</div></div><div><span>non secure boot</span></div></div><div><div><div>5</div></div><div><span>Jump</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>enter bootloader...</span></div></div><div><div><div>8</div></div><div><span>crpm init enter</span></div></div><div><div><div>9</div></div><div><span>crpm init done</span></div></div><div><div><div>10</div></div><div><span>ddr init enter, rate is 1333 Mbps</span></div></div><div><div><div>11</div></div><div><span>5ddr init done</span></div></div><div><div><div>12</div></div><div><span>serial init start</span></div></div><div><div><div>13</div></div><div><span>serial init done</span></div></div><div><div><div>14</div></div><div><span>SPI NAND</span></div></div><div><div><div>15</div></div><div><span>non secure uboot</span></div></div><div><div><div>16</div></div><div><span>backup header!!</span></div></div><div><div><div>17</div></div><div><span>Jump</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>.............</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>最后停在<code>Starting kernel ...</code>代表系统已经成功启动。</p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://blog.zlion.top/posts/lenovo-pro16-2021-amd-hackintosh/</id>
      <title type="text">联想小新 Pro16 2021 AMD 黑苹果安装教程</title>
      <published>2023-06-02T00:00:00.000Z</published>
      <updated>2023-06-02T00:00:00.000Z</updated>
      <author><name>Shiyan</name></author>
      <link rel="alternate" href="https://blog.zlion.top/posts/lenovo-pro16-2021-amd-hackintosh/"/>
      <summary type="text">面向联想小新 Pro16 2021 AMD 版的黑苹果安装、EFI 调整和 OpenCore 启动项配置教程。</summary>
      <content type="html"><![CDATA[<blockquote><p>本文档面向联想小新 Pro16 2021 AMD 版用户，整理黑苹果安装、EFI 调整与 OpenCore 启动项配置流程。
<strong>适用机型：</strong> 联想小新 Pro16 2021 AMD（5600H / 5800H）
<strong>适用系统：</strong> macOS Big Sur / Monterey / Ventura 13.x（取决于镜像与 EFI 支持）</p></blockquote>
<hr />
<section><h2>1. 准备工作<a href="#1-准备工作"><span>#</span></a></h2><div><div><div></div><div>重要</div></div><div><p>开始前请先确认硬盘型号不在不兼容列表内，并备份 Windows 与重要数据。调整 ESP 分区和抹盘都有数据风险。</p><p>硬盘兼容列表参考：<a href="https://macoshome.com/hackintosh/hcourse/2476.html" target="_blank">https://macoshome.com/hackintosh/hcourse/2476.html</a></p></div></div><section><h3>1.1 下载所需文件<a href="#11-下载所需文件"><span>#</span></a></h3>

<table><thead><tr><th>文件</th><th>地址</th><th>说明</th></tr></thead><tbody><tr><td>EFI</td><td><code>https://wwbnc.lanzoub.com/b007up95be</code>，密码：<code>zlion</code></td><td>5600H 与 5800H 使用不同 EFI，请按机型替换</td></tr><tr><td>macOS 镜像</td><td><code>https://cloud.189.cn/t/BbyEFnE7FBJb</code>，访问码：<code>uzx8</code></td><td>安装版本取决于镜像</td></tr><tr><td>DiskGenius</td><td><code>https://www.diskgenius.cn/</code></td><td>用于检查硬盘、调整 ESP 分区、复制 EFI</td></tr></tbody></table></section><section><h3>1.2 检查 BIOS<a href="#12-检查-bios"><span>#</span></a></h3><ol>
<li>开机按 <code>F2</code> 进入 BIOS。</li>
<li>关闭「快速启动」和「安全启动」。</li>
</ol></section><section><h3>1.3 调整 ESP 与 macOS 分区<a href="#13-调整-esp-与-macos-分区"><span>#</span></a></h3><div><div><div></div><div>建议</div></div><div><p>Windows 默认 ESP 分区通常只有 100 MB。如果无法直接扩容，可进入微 PE 后使用 DiskGenius 调整分区。</p></div></div><ol>
<li>如果 ESP 小于 200 MB，可从 C 盘左侧腾出约 100 MB，再合并到 ESP 分区。</li>
<li>单独划分一个用于安装 macOS 的分区，建议 <code>60 GB</code> 以上；示例环境分配约 <code>160 GB</code>。</li>
</ol><p></p><figure><img alt="图 2：ESP调整" loading="lazy" width="1270" height="897" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-02.iTp5Z0i2_Z14COP0.webp" /><figcaption>图 2：ESP调整</figcaption></figure><p></p><hr /></section></section>
<section><h2>2. 制作启动 U 盘<a href="#2-制作启动-u-盘"><span>#</span></a></h2><section><h3>2.1 写入镜像<a href="#21-写入镜像"><span>#</span></a></h3><p>准备一个大于 <code>16 GB</code> 的 U 盘，使用 balenaEtcher 写入 macOS 镜像。</p><ol>
<li>打开 balenaEtcher。</li>
<li>选择下载好的 macOS 镜像。</li>
<li>选择目标 U 盘。</li>
<li>点击 <code>Flash</code>，等待出现 <code>Complete</code>。</li>
</ol></section><section><h3>2.2 处理 EFI 文件<a href="#22-处理-efi-文件"><span>#</span></a></h3><ol>
<li>使用 OCAT 打开 <code>EFI/OC/config.plist</code>。</li>
<li>取消勾选 <code>Nootedred.kext</code>，保存配置。</li>
</ol><p></p><figure><img alt="图 3：取消勾选 Nootedred.kext" loading="lazy" width="1080" height="642" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-03.vrx575vJ_ZGH9uS.webp" /><figcaption>图 3：取消勾选 Nootedred.kext</figcaption></figure><p></p><ol>
<li>打开 DiskGenius，选中 U 盘的 EFI 分区。</li>
<li>将调整后的 EFI 文件夹完整复制进去，最终路径应类似：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>EFI(X:)/EFI/OC</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h2>3. 开始安装 macOS<a href="#3-开始安装-macos"><span>#</span></a></h2><div><div><div></div><div>警告</div></div><div><p>抹掉分区会清空目标分区数据。请确认选择的是为 macOS 单独划出的分区，不要误选 Windows 分区。</p></div></div><ol>
<li>插入启动 U 盘，开机按 <code>F12</code>。</li>
<li>选择 USB 启动项。</li>
<li>在 OpenCore 菜单中选择 <code>Install macOS Big Sur</code> 并按 <code>Enter</code>。</li>
<li>等待代码加载完成，进入 macOS 实用工具界面。</li>
</ol><p></p><figure><img alt="图 4：OpenCore 启动菜单" loading="lazy" width="1080" height="684" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-04.Bud3wrYV_Z1JBJoc.webp" /><figcaption>图 4：OpenCore 启动菜单</figcaption></figure><p></p><p></p><figure><img alt="图 5：代码加载界面" loading="lazy" width="1270" height="951" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-05.C7MThSAB_Al6oL.webp" /><figcaption>图 5：代码加载界面</figcaption></figure><p></p><ol>
<li>打开「磁盘工具」。</li>
<li>选中之前划出的 macOS 分区，点击「抹掉」。</li>
<li>格式选择 <code>APFS</code>，完成后返回实用工具界面。</li>
<li>选择「安装 macOS」，按提示继续安装。</li>
</ol><p></p><figure><img alt="图 6：macOS 实用工具" loading="lazy" width="1080" height="884" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-06.ChEifH-g_VNxKc.webp" /><figcaption>图 6：macOS 实用工具</figcaption></figure><p></p><div><div><div></div><div>提示</div></div><div><p>安装过程中可能会重启 1 到 2 次。如果重启后回到 Windows，请再次按 <code>F12</code> 进入 U 盘启动项，并选择 <code>Installer</code> 继续安装。</p></div></div><hr /></section>
<section><h2>4. 调整 EFI 文件<a href="#4-调整-efi-文件"><span>#</span></a></h2><p>完成安装后，需要调整 EFI，避免每次启动都显示冗长代码，并为本机生成独立序列号。</p><ol>
<li>打开最初未修改的 EFI 文件。</li>
<li>使用 OCAT 打开本机适配 EFI：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>联想小新 Pro16 2021 5600H/EFI/OC/config.plist</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>找到 <code>NVRAM</code> 第三行，双击编辑并删除对应启动参数-v。</li>
</ol><p></p><figure><img alt="去掉启动界面跑码参数" loading="lazy" width="2560" height="1513" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-11.DKd-EKlB_Z2wmGup.webp" /><figcaption>去掉启动界面跑码参数</figcaption></figure><p></p><p></p><figure><img alt="图 7：正常开机无跑码" loading="lazy" width="1270" height="951" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-07.BKeZVjFF_Z245Qgi.webp" /><figcaption>图 7：正常开机无跑码</figcaption></figure><p></p><ol>
<li>生成并写入本机专属序列号，避免多人共用同一套序列号。</li>
<li>保存修改后的最终 EFI。</li>
</ol><p></p><figure><img alt="图 8：OCAT 调整 序列号" loading="lazy" width="1266" height="750" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-08.BanbKPJd_ZLkK8Q.webp" /><figcaption>图 8：OCAT 调整 序列号</figcaption></figure><p></p><hr /></section>
<section><h2>5. 设置 OpenCore 启动项<a href="#5-设置-opencore-启动项"><span>#</span></a></h2><p>设置完成后，就不需要每次插 U 盘进入 macOS。</p><ol>
<li>回到 Windows。</li>
<li>使用 DiskGenius 打开硬盘 ESP 分区。</li>
<li>将最终 EFI 中的 <code>OC</code> 文件夹完整复制到：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ESP/EFI/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>复制后，<code>OC</code> 应与原本的 <code>Microsoft</code>、<code>Boot</code> 文件夹并列。</p><p></p><figure><img alt="图 9：硬盘 ESP 中的 OC 文件夹" loading="lazy" width="1266" height="893" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-09.Sfb2YCzo_Z1rBuDy.webp" /><figcaption>图 9：硬盘 ESP 中的 OC 文件夹</figcaption></figure><p></p><ol>
<li>在 DiskGenius 工具栏选择「设置 UEFI BIOS 启动项」。</li>
<li>点击「添加」，按以下路径选中 <code>OpenCore.efi</code>：</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ESP/EFI/OC/OpenCore.efi</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>保存启动项。</li>
<li>将新增的 OpenCore 启动项移动到最上方，作为默认启动项。</li>
</ol><p></p><figure><img alt="图 10：添加 OpenCore UEFI 启动项" loading="lazy" width="1268" height="905" src="/_astro/lenovo-pro16-2021-amd-hackintosh-figure-10.BGRVjO78_1KauBF.webp" /><figcaption>图 10：添加 OpenCore UEFI 启动项</figcaption></figure><p></p><div><div><div></div><div>建议</div></div><div><p>日常默认进入 macOS；需要进入 Windows 时，开机按 <code>F12</code> 手动选择 Windows 启动项。</p></div></div><hr /></section>
<section><h2>6. 补充注意事项<a href="#6-补充注意事项"><span>#</span></a></h2><ol>
<li>2021 款小新 Pro16 存在网卡限制：自带有线网卡无法驱动，不能直插网线。</li>
<li>可通过 USB 网卡或手机 USB 网络共享上网，iPhone 通常无需额外驱动。</li>
<li>不要在 macOS 内直接在线更新系统大版本，否则可能导致无法开机或引导失效。</li>
<li>如需交流，可加入 QQ 群：<a href="https://qm.qq.com/q/y1tjknEfcG" target="_blank">https://qm.qq.com/q/y1tjknEfcG</a>。</li>
</ol><hr /></section>
<section><h2>7. 故障排查<a href="#7-故障排查"><span>#</span></a></h2>

<table><thead><tr><th>问题</th><th>处理方式</th></tr></thead><tbody><tr><td>OpenCore 菜单一闪进入 Windows</td><td>开机后尽快用方向键选择 macOS 安装项，或重新检查 U 盘启动顺序</td></tr><tr><td>安装中途重启回 Windows</td><td>再次按 <code>F12</code> 进入 U 盘，选择 <code>Installer</code> 继续安装</td></tr><tr><td>找不到 OpenCore 启动项</td><td>检查 <code>ESP/EFI/OC/OpenCore.efi</code> 是否存在，并重新添加 UEFI 启动项</td></tr><tr><td>macOS 无法联网</td><td>使用 USB 网卡或手机 USB 网络共享</td></tr><tr><td>更新后无法启动</td><td>不建议在线升级大版本；恢复原 EFI 或重新制作安装 U 盘排查</td></tr></tbody></table><hr /></section>
<section><h2>8. 视频演示<a href="#8-视频演示"><span>#</span></a></h2>
<hr /></section>]]></content>
    </entry>
</feed>
