feat: 开放小爱音箱补丁固件构建源代码和教程

This commit is contained in:
Del Wang
2025-04-12 23:49:07 +08:00
parent 0585ddab72
commit 2cf6623db9
21 changed files with 1794 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
# 排除全部文件
**/*
# 只保留构建相关的文件
!src
!package.json
!package-lock.json
!tsconfig.json
+9
View File
@@ -0,0 +1,9 @@
# 你的小米账号/密码
MI_USER=23333333
MI_PASS=xxxxxxxxx
# 你的小爱音箱名称/DID
MI_DID=小爱音箱Pro
# 你的 SSH 登录密码(默认为 open-xiaoai
SSH_PASSWORD=open-xiaoai
+3
View File
@@ -0,0 +1,3 @@
temp
assets/*
!assets/.gitkeep
+20
View File
@@ -0,0 +1,20 @@
FROM node:22-alpine
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone \
&& apk add --no-cache \
file \
patch \
python3 \
openssl \
squashfs-tools
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "run", "build"]
+87
View File
@@ -0,0 +1,87 @@
# OpenXiaoAI Patch
> [!CAUTION]
> 刷机有风险,操作需谨慎。
小爱音箱 Pro 补丁固件制作流程:
- 固件提取(登录小米账号获取 OTA 链接)
- 开启固化 SSH(支持自定义登录密码)
- 禁用系统自动更新(系统更新后需要重新刷机打补丁)
- 添加开机启动脚本 `/data/init.sh`(方便执行一些初始化脚本)
## 准备条件
> [!IMPORTANT]
> 本教程仅适用于 **小爱音箱 ProLX06** 和 **Xiaomi 智能音箱 ProOH2P** 这两款机型,**其他型号**的小爱音箱可能存在兼容性问题,请勿直接使用!🚨
- Windows 电脑(需为 x86_64 架构,用于刷机)
- 数据线(不能只是充电线,需要连接到电脑传输数据)
- Type-C(适用于新款小爱音箱 Pro,无需拆机)
- Micro USB(旧款小爱音箱 Pro 用这种,需要拆机)
## 下载固件
> [!NOTE]
> 构建版本待发布,敬请期待。
你可以直接在 Github Release 页面下载打包好的补丁固件。
> 默认 SSH 登录密码为 `open-xiaoai`
## 制作固件
或者你也可以按照下面的 2 种方法,制作自定义固件。
### 基础配置
修改 `.env.example` 文件里的配置,然后重命名为 `.env`
```shell
# 你的小米账号/密码
MI_USER=23333333
MI_PASS=xxxxxxxxx
# 你的小爱音箱名称/DID
MI_DID=小爱音箱Pro
# 你的 SSH 登录密码(默认为 open-xiaoai
SSH_PASSWORD=open-xiaoai
```
### 1. 使用 Docker 打包固件(推荐)
> [!NOTE]
> Docker 镜像待发布,敬请期待。
```shell
# 使用 Docker 进行构建
docker run -it --rm --env-file $(pwd)/.env -v $(pwd)/assets:/app/assets -v $(pwd)/patches:/app/patches open-xiaoai
# ✅ 打包完成,固件文件已复制到 assets 目录...
# /app/assets/mico_all_92db90ed6_1.88.197/root.squashfs
```
### 2. 本地构建(macOS、Linux
为了能够正常编译运行该项目,你需要安装以下依赖环境:
- Dockerhttps://www.docker.com/get-started/
- Pythonhttps://www.python.org/downloads/
- Node.js: https://nodejs.org/zh-cn/download
```bash
# 安装依赖
npm install
# 打包固件
npm run build
# ✅ 打包成功后,原始固件和补丁固件会保存在 assets 目录下
```
> [!TIP]
> 如果你想要更进一步的定制自己的固件,可以参考 `src/build.sh` 脚本里的构建流程:在提取固件后自行修改固件内的脚本、配置和应用程序,然后重新打包即可。
+893
View File
@@ -0,0 +1,893 @@
{
"name": "open-xiaoai-patch",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-xiaoai-patch",
"version": "1.0.0",
"dependencies": {
"@mi-gpt/miot": "^1.0.0"
},
"devDependencies": {
"@mi-gpt/config": "1.0.0",
"@types/node": "^22.13.11",
"tsx": "^4.19.3",
"typescript": "^5.8.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@mi-gpt/config": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mi-gpt/config/-/config-1.0.0.tgz",
"integrity": "sha512-oo/roYiRcGiHsZESjukwPTsRFqddShTJ8gkccR5ZMYn8SySj07A0nCsEsLEfB9mLkvNY8D6PCO1NxbRDDRBBlA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/@mi-gpt/miot": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mi-gpt/miot/-/miot-1.0.0.tgz",
"integrity": "sha512-smWhddHzK1du3/LW2FifBrIIyeZNx/RvTSe0Zi4v3lYZwK8NeEwomg1jBjktQCCV4Fh8OuqNbCdNbmiVXmr5kw==",
"license": "MIT",
"dependencies": {
"@mi-gpt/utils": "1.0.0",
"axios": "^1.8.4",
"pako": "^2.1.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/@mi-gpt/utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mi-gpt/utils/-/utils-1.0.0.tgz",
"integrity": "sha512-hAU/uYPNiNJgXWkQrVqRLe/9Uz8AgtoiNwcbbwQs8iXJcyq3NjcSHkkwUseJAnaQFE9iv/kvHZZsx3r2HhlKjw==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/@types/node": {
"version": "22.14.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.2",
"@esbuild/android-arm": "0.25.2",
"@esbuild/android-arm64": "0.25.2",
"@esbuild/android-x64": "0.25.2",
"@esbuild/darwin-arm64": "0.25.2",
"@esbuild/darwin-x64": "0.25.2",
"@esbuild/freebsd-arm64": "0.25.2",
"@esbuild/freebsd-x64": "0.25.2",
"@esbuild/linux-arm": "0.25.2",
"@esbuild/linux-arm64": "0.25.2",
"@esbuild/linux-ia32": "0.25.2",
"@esbuild/linux-loong64": "0.25.2",
"@esbuild/linux-mips64el": "0.25.2",
"@esbuild/linux-ppc64": "0.25.2",
"@esbuild/linux-riscv64": "0.25.2",
"@esbuild/linux-s390x": "0.25.2",
"@esbuild/linux-x64": "0.25.2",
"@esbuild/netbsd-arm64": "0.25.2",
"@esbuild/netbsd-x64": "0.25.2",
"@esbuild/openbsd-arm64": "0.25.2",
"@esbuild/openbsd-x64": "0.25.2",
"@esbuild/sunos-x64": "0.25.2",
"@esbuild/win32-arm64": "0.25.2",
"@esbuild/win32-ia32": "0.25.2",
"@esbuild/win32-x64": "0.25.2"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-tsconfig": {
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
"integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/tsx": {
"version": "4.19.3",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz",
"integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "open-xiaoai-patch",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"ota": "tsx --env-file=.env src/ota.ts",
"extract": "sh src/extract.sh",
"patch": "sh src/patch.sh",
"squashfs": "sh src/squashfs.sh",
"build": "sh src/build.sh"
},
"dependencies": {
"@mi-gpt/miot": "^1.0.0"
},
"devDependencies": {
"@mi-gpt/config": "1.0.0",
"@types/node": "^22.13.11",
"tsx": "^4.19.3",
"typescript": "^5.8.2"
}
}
@@ -0,0 +1,48 @@
--- squashfs-root/etc/init.d/dropbear 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/init.d/dropbear 2025-02-13 21:19:31
@@ -53,8 +53,7 @@
MaxAuthTries mdns ipaddrs
validate_section_dropbear "${1}" || {
- echo "validation failed"
- return 1
+ echo "validation failed and skip"
}
[ -n "${Interface}" ] && {
@@ -64,7 +63,6 @@
}
}
- [ "${enable}" = "0" ] && return 1
PIDCOUNT="$(( ${PIDCOUNT} + 1))"
local pid_file="/var/run/${NAME}.${PIDCOUNT}.pid"
@@ -111,25 +109,12 @@
load_interfaces()
{
config_get interface "$1" Interface
- config_get enable "$1" enable 1
-
- [ "${enable}" = "1" ] && interfaces=" ${interface} ${interfaces}"
+ interfaces=" ${interface} ${interfaces}"
}
start_service()
{
- [ x"bluetooth" == x`micocfg_work_mode` ] && {
- logger -t dropbear.init.d -p 3 "in bluetooth mode, will not start"
- return
- }
-
- ssh_en=`cat /data/ssh_en 2>/dev/null`
- ssh_en_bind=`cat /data/.ssh_en 2>/dev/null`
- ssh_en_tmp=`cat /tmp/ssh_en 2>/dev/null`
- channel=`micocfg_channel 2>/dev/null`
- if [ "$ssh_en" != "1" -a "$ssh_en_bind" != "1" -a "$ssh_en_tmp" != "1" -a "$channel" = "release" ]; then
- return 0
- fi
+ mkdir -p /data/etc/dropbear
[ -s /data/etc/dropbear/dropbear_rsa_host_key ] || keygen
. /lib/functions.sh
@@ -0,0 +1,35 @@
--- squashfs-root/etc/shadow 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/shadow 2025-02-13 21:07:41
@@ -1,4 +1,4 @@
-root:$1$N0Iz0LLs$kZ5zG/Y2AUWHNE5I3ElWm1:18128:0:99999:7:::
+root:{SSH_PASSWORD}:18128:0:99999:7:::
daemon:*:0:0:99999:7:::
ftp:*:0:0:99999:7:::
network:*:0:0:99999:7:::
diff --color -Naur squashfs-root/etc/inittab squashfs-root-patched/etc/inittab
--- squashfs-root/etc/inittab 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/inittab 2025-02-13 20:56:13
@@ -1,5 +1,5 @@
::sysinit:/etc/init.d/rcS S boot
::shutdown:/etc/init.d/rcS K shutdown
tts/0::askfirst:/bin/ash --login
-ttyS0::askfirst:/bin/login
+ttyS0::askfirst:/bin/ash --login
tty1::askfirst:/bin/ash --login
diff --color -Naur squashfs-root/etc/pam.d/common-auth squashfs-root-patched/etc/pam.d/common-auth
--- squashfs-root/etc/pam.d/common-auth 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/pam.d/common-auth 2025-02-13 21:09:02
@@ -8,10 +8,10 @@
# traditional Unix authentication mechanisms.
#
-auth sufficient libmico-pam.so
-account sufficient libmico-pam.so
+# auth sufficient libmico-pam.so
+# account sufficient libmico-pam.so
# here are the per-package modules (the "Primary" block)
-# auth [success=1 default=ignore] pam_unix.so nullok_secure
+auth [success=1 default=ignore] pam_unix.so nullok_secure
# here's the fallback if no module succeeds
auth requisite pam_deny.so
# prime the stack with a positive return value if there isn't one already;
@@ -0,0 +1,46 @@
--- squashfs-root/bin/ota 2024-12-06 15:11:58
+++ squashfs-root-patched/bin/ota 2025-02-13 21:50:40
@@ -133,6 +133,9 @@
download_upgrade() {
+ logger -t ota -p 3 "download upgrade failed"
+ return 1
+
if [ -f $OTA_FILE ]; then
logger -t ota -p 3 "ota is in progress now, exit this one."
exit 1
@@ -278,6 +281,9 @@
}
upgrade() {
+ logger -t ota -p 3 "no upgrade"
+ return 1
+
if [ -z "$1" -o -z "$2" ]; then
upgrade_failed
return 1
--- squashfs-root/bin/flash.sh 2024-12-06 15:11:58
+++ squashfs-root-patched/bin/flash.sh 2025-02-18 23:21:57
@@ -5,6 +5,9 @@
. /bin/boardupgrade.sh
upgrade_param_check() {
+ klogger "no upgrade"
+ return 1
+
if [ -z "$1" -o ! -f "$1" ]; then
klogger "USAGE: $0 input.bin [1: slient] [1:don't reboot]"
return 1
--- squashfs-root/etc/init.d/wireless 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/init.d/wireless 2025-02-18 23:15:31
@@ -1361,6 +1361,9 @@
password=$3
identity=$4
+ echo "download_fail"
+ exit 3;
+
trylock "ota"
[ $? != 0 ] && {
# shut_led 10
@@ -0,0 +1,9 @@
--- squashfs-root/etc/rc.local 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/rc.local 2025-02-13 22:06:13
@@ -1,4 +1,6 @@
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.
+[ -f "/data/init.sh" ] && /data/init.sh &
+
exit 0
@@ -0,0 +1,33 @@
--- squashfs-root/etc/asound.conf 2024-12-06 15:11:58
+++ squashfs-root-patched/etc/asound.conf 2025-02-18 22:14:10
@@ -125,16 +125,20 @@
}
pcm.noop {
- type dsnoop
- ipc_key 1024
- slave {
- pcm "hw:0,3"
- format S32_LE
- rate 48000
- channels 8
- period_size 480
- buffer_size 3840
- }
+ type plug
+ slave.pcm {
+ type dsnoop
+ ipc_key 1024
+ ipc_perm 0666
+ slave {
+ pcm "hw:0,3"
+ format S32_LE
+ rate 48000
+ channels 8
+ period_size 480
+ buffer_size 3840
+ }
+ }
}
defaults.pcm.rate_converter "speexrate_medium"
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -e
# 动态获取偏移量
OFFSET=$(strings -t d usr/lib/libxaudio_engine.so | grep hw:0,3 | awk '{print $1}')
# 检查偏移量是否存在
if [ -z "$OFFSET" ]; then
echo "未找到 \"hw:0,3\" 字符串偏移量,取消 patch usr/lib/libxaudio_engine.so"
exit -1
fi
echo -n -e "noop\x00\x00" | dd of=usr/lib/libxaudio_engine.so bs=1 count=6 seek=$OFFSET conv=notrunc
echo "patched file usr/lib/libxaudio_engine.so"
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
set -e
# 下载固件
if [ -z "$CI" ]; then
npm run ota
else
npx tsx src/ota.ts
fi
# 提取固件
npm run extract
# 打补丁
npm run patch
# 打包固件
npm run squashfs
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Source: https://github.com/csftech/Xiaomi-OpenWrt-firmware-toolkit
# Author: @csftech (Sheng-Fu Chang)
# MIT Licensed
import argparse
import binascii
import ctypes
import hashlib
import logging
import os
import time
class ImageHeader(ctypes.Structure):
_fields_ = [
("magic", ctypes.c_uint),
("signature_offset", ctypes.c_uint),
("crc32_checksum", ctypes.c_uint),
("file_type", ctypes.c_ushort),
("model", ctypes.c_ushort),
("segment_offsets", ctypes.c_uint * 8),
]
class SegmentHeader(ctypes.Structure):
_fields_ = [
("magic", ctypes.c_uint),
("flash_address", ctypes.c_uint),
("length", ctypes.c_uint),
("partition", ctypes.c_uint),
("segment_name", ctypes.c_char * 32),
]
class Firmware:
def __init__(self, path):
self.path = path
self.image_header = ImageHeader()
self.fd = open(self.path, "rb")
def verify(self, ignore_hash=False):
logging.info("[Jobs] Verifying firmware image...")
assert self.fd.readinto(self.image_header) == ctypes.sizeof(self.image_header)
# magic
logging.info(f"firmware magic: {hex(self.image_header.magic)}")
assert self.image_header.magic in [0x31524448, 0x32524448]
if self.image_header.magic == 0x32524448:
logging.info("NOTE: firmware seems to be encrypted?")
# signature
self.fd.seek(self.image_header.signature_offset)
signature_length = int.from_bytes(self.fd.read(16), "little")
signature = self.fd.read()
logging.info(
f"firmware signature_offset: {hex(self.image_header.signature_offset)}"
)
logging.info(f"firmware signature_length: {signature_length}")
logging.info(f"firmware signature: {signature.hex()}")
assert len(signature) == signature_length
# crc32
self.fd.seek(12)
logging.info(
f"firmware crc32_checksum: {hex(self.image_header.crc32_checksum)}"
)
computed_checksum = ~binascii.crc32(self.fd.read()) & 0xFFFFFFFF
logging.info(f"computed crc32_checksum: {hex(computed_checksum)}")
assert self.image_header.crc32_checksum == computed_checksum
# md5
self.fd.seek(0)
m = hashlib.md5()
m.update(self.fd.read())
hash = m.hexdigest()
logging.info(f"computed md5 hash: {hash}")
filename_hash = os.path.basename(self.path).split("_")[-2]
try:
assert hash[(len(filename_hash) * -1) :] == filename_hash
except AssertionError:
if ignore_hash:
logging.warning(
f'Warning: hash "{filename_hash}" does not match expected!'
)
pass
else:
raise
self.fd.seek(0)
return True
def extract(self, dest: str = None):
logging.info("[Jobs] Extracting firmware...")
current_time = time.strftime("%Y%m%d_%H%M%S", time.localtime())
self.dest_dir = os.path.join(
dest, f'{os.path.basename(self.path).replace(".bin", "")}_{current_time}'
)
if dest is not None:
self.dest_dir = dest
os.mkdir(self.dest_dir)
logging.info(f"create destination directory: {self.dest_dir}")
for address in self.image_header.segment_offsets:
if address:
self.fd.seek(address)
segment_header = SegmentHeader()
assert self.fd.readinto(segment_header) == ctypes.sizeof(segment_header)
# extract segment
with open(
os.path.join(self.dest_dir, segment_header.segment_name.decode()),
"wb",
) as s:
s.write(self.fd.read(segment_header.length))
logging.info(
f'extracting segment: {segment_header.segment_name.decode("ascii")}'
)
def run(path, extract=False, dest=None, ignore_hash=False):
logging.info(f"[MSG] Input file: {path}")
firmware = Firmware(path)
if firmware.verify(ignore_hash):
logging.info(
"[Jobs] Verification success: it's a genuine firmware from Xiaomi."
)
if extract:
firmware.extract(dest)
logging.info("[Jobs] Extraction complete.")
if __name__ == "__main__":
logging.basicConfig(format="%(asctime)s - %(message)s", level=logging.INFO)
parser = argparse.ArgumentParser()
parser.add_argument(
"-d",
"--dest",
help="Destination directory to store extracted files, default=current working directory",
action="store",
)
parser.add_argument(
"-i",
"--ignore-hash",
help="Do not fail if md5 hash does not match",
action="store_true",
)
parser.add_argument("-e", "--extract", help="Input your firmware", action="store")
parser.add_argument("-s", "--show", help="Show firmware info", action="store")
args = parser.parse_args()
if args.extract:
firmware_path = os.path.abspath(args.extract)
dest_path = os.getcwd()
if args.dest:
dest_path = args.dest
run(firmware_path, extract=True, dest=dest_path, ignore_hash=args.ignore_hash)
elif args.show:
firmware_path = os.path.abspath(args.show)
run(firmware_path, ignore_hash=args.ignore_hash)
else:
parser.print_help()
+21
View File
@@ -0,0 +1,21 @@
#!/bin/bash
set -e
BASE_DIR=$(pwd)
WORK_DIR=$BASE_DIR/temp
FIRMWARE=$(basename $(ls $BASE_DIR/assets/*.bin 2>/dev/null | head -n 1) .bin)
if [ ! -f "$BASE_DIR/assets/$FIRMWARE.bin" ]; then
echo "❌ 固件文件不存在,请先下载固件到:$BASE_DIR/assets/"
exit 1
fi
rm -rf "$WORK_DIR" && mkdir -pv "$WORK_DIR" && cd $WORK_DIR
python3 $BASE_DIR/src/extract.py -e "$BASE_DIR/assets/$FIRMWARE.bin" -d "$WORK_DIR/$FIRMWARE"
ln -sf $WORK_DIR/$FIRMWARE/root.squashfs $WORK_DIR/root.squashfs
unsquashfs $WORK_DIR/root.squashfs
+261
View File
@@ -0,0 +1,261 @@
import { getMiNA } from "@mi-gpt/miot";
import { createHash } from "node:crypto";
import * as fs from "node:fs";
import * as path from "node:path";
import * as stream from "node:stream";
import { promisify } from "node:util";
const kSupportedDevices = [
"LX06", // 小爱音箱 Pro
"OH2P", // Xiaomi 智能音箱 Pro
];
// 获取 OTA 信息
async function getOTA(channel: "release" | "current" | "stable" = "release") {
const MiNA = await getMiNA({
userId: process.env.MI_USER!,
password: process.env.MI_PASS!,
});
if (!MiNA) {
console.log(`❌ 登录失败,请检查小米账号密码是否正确:`);
console.log(`当前账号:${process.env.MI_USER}`);
console.log(`当前密码:${process.env.MI_PASS}`);
return;
}
const devices = await MiNA.getDevices();
const speaker = devices?.find(
(e: any) =>
e.name === process.env.MI_DID || e.miotDID === process.env.MI_DID
);
if (!speaker) {
console.log(`❌ 找不到设备: ${process.env.MI_DID}`);
console.log(`可用设备列表:`);
console.log(
JSON.stringify(
devices.map((e: any) => ({
name: e.name,
did: e.miotDID,
})),
null,
4
)
);
return;
}
if (!kSupportedDevices.includes(speaker.hardware)) {
console.log(
`❌ 暂不支持当前设备型号: ${speaker.hardware}${speaker.name}`
);
console.log(`可用设备列表:`);
console.log(
JSON.stringify(
devices
.filter((e: any) => kSupportedDevices.includes(e.hardware))
.map((e: any) => ({
name: e.name,
did: e.miotDID,
})),
null,
4
)
);
return;
}
const sn = speaker.serialNumber;
const model = speaker.hardware;
const version = speaker.romVersion;
const time = new Date().getTime();
const otaInfo = `channel=${channel}&filterID=${sn}&locale=zh_CN&model=${model}&time=${time}&version=${version}&8007236f-a2d6-4847-ac83-c49395ad6d65`;
const base64Str = Buffer.from(otaInfo).toString("base64");
const code = createHash("md5").update(base64Str).digest("hex");
return {
sn,
model,
version,
url: `http://api.miwifi.com/rs/grayupgrade/v2/${model}?model=${model}&version=${version}&channel=${channel}&filterID=${sn}&locale=zh_CN&time=${time}&s=${code}`,
};
}
async function main() {
console.log(`🔥 正在获取设备信息...`);
const ota = await getOTA();
if (!ota) {
console.log(`❌ 获取设备信息失败`);
process.exit(1);
}
console.log(`🔥 正在获取 OTA 信息...`);
const res = await fetch(ota.url);
const data = await res.json();
if (data.code === "0" && data.data) {
if (data.data.currentInfo) {
console.log("\n=== 当前版本固件 ===\n");
const filePath = await downloadFirmware(data.data.currentInfo);
if (filePath) {
fs.writeFileSync(
path.join(process.cwd(), "assets", ".model"),
ota.model
);
fs.writeFileSync(
path.join(process.cwd(), "assets", ".version"),
ota.version
);
}
}
} else {
console.log(`❌ 获取固件信息失败: ${data.code || "未知错误"}`);
process.exit(1);
}
}
main();
// 下载固件
async function downloadFirmware(firmware: {
link: string;
hash: string;
toVersion?: string;
size?: number;
}): Promise<string | undefined> {
if (!firmware || !firmware.link) {
console.log("❌ 无效的固件信息");
return;
}
const assetsDir = path.join(process.cwd(), "assets");
// 从链接中提取文件名
const url = new URL(firmware.link);
const filename = path.basename(url.pathname);
// 打印固件信息
console.log(`- 版本: ${firmware.toVersion || "未知"}`);
console.log(
`- 大小: ${
firmware.size ? (firmware.size / 1024 / 1024).toFixed(2) + "MB" : "未知"
}`
);
console.log(`- 文件: ${filename}`);
console.log(`- MD5: ${firmware.hash || "未知"}\n`);
try {
const filePath = await downloadFile(firmware.link, assetsDir, filename);
return filePath;
} catch (error) {
console.error(`❌ 下载固件失败: ${error}`);
return;
}
}
async function ensureDir(dirPath: string): Promise<void> {
try {
await fs.promises.access(dirPath);
} catch (error) {
await fs.promises.mkdir(dirPath, { recursive: true });
}
}
async function downloadFile(
url: string,
destDir: string,
filename: string
): Promise<string> {
await ensureDir(destDir);
const destPath = path.join(destDir, filename);
// 检查文件是否已存在
try {
await fs.promises.access(destPath);
console.log(`️ 文件已存在: ${destPath}`);
return destPath;
} catch (error) {
// 文件不存在,下载它
console.log(`⬇️ 开始下载: ${url}`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
}
// 获取文件大小
const contentLength = response.headers.get("content-length");
const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
// 创建文件写入流
const fileStream = fs.createWriteStream(destPath);
if (!response.body) {
throw new Error("下载失败: 无法获取响应体");
}
// 创建进度显示变量
let downloadedBytes = 0;
let lastLoggedPercent = -1;
let startTime = Date.now();
// 使用 TransformStream 来跟踪下载进度
const progressStream = new stream.Transform({
transform(chunk, encoding, callback) {
downloadedBytes += chunk.length;
if (totalSize) {
const percent = Math.floor((downloadedBytes / totalSize) * 100);
// 确保每增加1%才打印一次,避免日志过多
if (percent > lastLoggedPercent) {
lastLoggedPercent = percent;
// 格式化输出
const downloaded = (downloadedBytes / 1024 / 1024).toFixed(2);
const total = (totalSize / 1024 / 1024).toFixed(2);
// 使用\r使光标回到行首,实现同行更新
process.stdout.write(
`\r下载进度: ${percent}% | ${downloaded}MB/${total}MB`
);
// 如果下载完成,打印换行符
if (downloadedBytes === totalSize) {
process.stdout.write("\n");
}
}
} else {
// 无法获取总大小时,只显示已下载大小
if (downloadedBytes % (1024 * 1024) === 0) {
// 每1MB打印一次
const downloaded = (downloadedBytes / 1024 / 1024).toFixed(2);
process.stdout.write(`\r已下载: ${downloaded}MB`);
}
}
this.push(chunk);
callback();
},
});
// 完成后清理并打印最终结果
progressStream.on("end", () => {
const totalTime = ((Date.now() - startTime) / 1000).toFixed(2);
console.log(`\n✅ 下载完成: ${destPath}`);
console.log(
` 总大小: ${(downloadedBytes / 1024 / 1024).toFixed(
2
)}MB, 用时: ${totalTime}`
);
});
// 使用pipeline连接流
await promisify(stream.pipeline)(
// @ts-ignore - response.body 在 Node.js 中是 ReadableStream
response.body,
progressStream,
fileStream
);
return destPath;
}
}
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
set -e
BASE_DIR=$(pwd)
WORK_DIR=$BASE_DIR/temp
PASSWORD=${SSH_PASSWORD:-"open-xiaoai"}
PASSWORD=$(openssl passwd -1 -salt "open-xiaoai" "$PASSWORD")
# 应用指定目录下的补丁文件
apply_patches() {
local patch_dir="$1"
local message="$2"
echo "🔥 $message"
if [ -d "$patch_dir" ]; then
for file in "$patch_dir"/*; do
if [ -f "$file" ]; then
if [[ "$file" == *.patch ]]; then
# 创建临时文件用于占位符替换
local temp_patch=$(mktemp)
# 将补丁文件中的 {SSH_PASSWORD} 替换为 PASSWORD
sed "s/{SSH_PASSWORD}/$PASSWORD/g" "$file" > "$temp_patch"
# 应用替换后的补丁
patch -p1 < "$temp_patch"
# 清理临时文件
rm "$temp_patch"
elif [[ "$file" == *.sh ]]; then
sh "$file"
fi
fi
done
fi
}
if [ ! -f "$BASE_DIR/assets/.model" ]; then
echo "❌ 固件信息不存在,请先下载固件到:$BASE_DIR/assets/"
exit 1
fi
PATCH_DIR=$BASE_DIR/patches
MODEL=$(cat $BASE_DIR/assets/.model)
cd $WORK_DIR/squashfs-root
# 应用通用补丁
apply_patches "$PATCH_DIR" "正在应用通用补丁..."
# 应用特定型号补丁
apply_patches "$PATCH_DIR/$MODEL" "正在应用 $MODEL 补丁..."
echo "✅ 补丁应用完成"
+36
View File
@@ -0,0 +1,36 @@
#!/bin/bash
set -e
BASE_DIR=$(pwd)
WORK_DIR=$BASE_DIR/temp
FIRMWARE=$(basename $(ls $BASE_DIR/assets/*.bin 2>/dev/null | head -n 1) .bin)
cd $WORK_DIR
if [ ! -f "$BASE_DIR/assets/$FIRMWARE.bin" ]; then
echo "❌ 固件文件不存在,请先下载固件到:$BASE_DIR/assets/"
exit 1
fi
if [ ! -d "$FIRMWARE" ]; then
echo "❌ 解压后的固件文件夹不存在,请先提取固件"
exit 1
fi
SQUASHFS_INFO=$(file $FIRMWARE/root.squashfs)
echo "🚗 原始固件信息: $SQUASHFS_INFO"
COMPRESSION=$(echo "$SQUASHFS_INFO" | grep -o "xz\|gzip\|lzo\|lz4\|zstd compressed" | cut -d' ' -f1)
BLOCKSIZE=$(echo "$SQUASHFS_INFO" | grep -o "blocksize: [0-9]* bytes" | cut -d' ' -f2)
echo "🔥 使用原始参数重新打包固件..."
mksquashfs squashfs-root $FIRMWARE/root.squashfs \
-comp $COMPRESSION -b $BLOCKSIZE \
-noappend -all-root -always-use-fragments -no-xattrs -no-exports
cp -rf $FIRMWARE $BASE_DIR/assets/$FIRMWARE
echo "✅ 打包完成,固件文件已复制到 assets 目录..."
echo $BASE_DIR/assets/$FIRMWARE/root.squashfs
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "@mi-gpt/config/typescript",
"include": ["src"],
"exclude": ["node_modules"]
}