Repository navigation
Releases: 0Sakura721/Kaze-SLauncher
Releases · 0Sakura721/Kaze-SLauncher
Release list
v0.5.0-beta.1
[0.5.0-beta.1] - 2026-10-05
Added
- AI 助手:全屏聊天页 + 独立设置页;流式输出(思考过程逐 token 实时显示,取消即时生效)
- AI 工具:读取/列出/写入实例文件(写入确认 + 自动 .bak 备份 + 敏感文件读取需确认 + 注入防护)、抓取网页正文、执行控制台命令
- 控制台命令三档控制(借鉴 Harness 权限分层):仅建议(默认)/ 白名单自动(查询类直接执行)/ 全部自动,所有执行记入审计日志(ai_audit.log)
- AI 记忆:read/write_memory 维护每实例的长期笔记,随每轮对话自动注入
- AI 联网搜索四源:Tavily / 博查 / SearXNG(免 Key 自建)/ 本机浏览器(悬浮窗授权走真实窗口),带 5 分钟结果缓存
- AI 配置:多模型档案(可建多份、单选分配),API Key 用 Keystore AES-GCM 加密存储,需要附加参数的服务商可自定义请求体 JSON
- 更新机制改为以发布日期为准(不再比较版本号),更新弹窗显示发布日期;预发布版本自动标记、stable 通道不推送
- AI 页新增:删除会话历史(带确认)、一键回到底部、token 用量与缓存命中显示
Fixed
- 原生批量工具调用挂起时丢结果(恢复后被服务端 400 拒绝)、未运行实例谎报已执行、路径显示未净化、恢复对话 id 撞车崩溃、超时/取消被阻塞读延迟最多 240 秒
- 控制台:AI 建议命令按消息记录的实例执行(切实例后不串台)、写文件区分脚本类高危警示
Changed
- DeepSeek 集成迁移到 V4 机制(deepseek-flash + thinking 参数控制思考;旧模型名官方已停用,自动归一)
- AI 回复的 token 用量与缓存命中随回答显示
v0.4.0-fix
[0.4.0-fix] - 2026-10-01
Fixed
- 修 v7a(MTK)部署失败现场被噪音淹没:厂商注入应用的
LD_PRELOAD(libdirect-coredump.so)不再带入 guest——此前 proot 把它带进 Ubuntu,guest 的 ld.so 给每个 guest 进程刷一条ERROR: ld.so: ... ignored.(非致命);失败消息改为滤掉该噪音并取输出末尾 300 字符(apt 的真错误在最后),下次失败能直接看到真原因 - 修诊断日志「闪退现场补捞」在老系统(MTK Android 9)整屏打印 logcat 用法帮助:
--uid=是老 logcat 不认识的参数,已去掉(应用跑 logcat 本就只能看到自己的条目)并加防护——再遇到不支持的参数不会把用法文本当日志尾巴记录
v0.4.0
[0.4.0] - 2026-10-01
Changed
- P2 收尾六处:后台启动守护失败不再无声(记入诊断日志)、守护服务存活期持部分唤醒锁(熄屏后 CPU 不再挂起服务端子进程)、下载重定向一律拒绝明文 http 且缺 Location 不漏连接、tar 解压加 8GB 写出上限 + 建链前越界校验 + 可取消、新建向导转屏保留核心/版本选择
- 修实例库扫描回写的覆盖竞态:扫描结果未变化不回写、扫描期间 instances.json 被外部改过则放弃回写(冷启动后台扫描不再可能盖掉并发写入的新数据,CI 偶发失败同源)
- P2 修复 18/19/20:备份按实例 id 归属、轮转后回读游标、Forge 老版本号解析
- P2 修复 15/16/17:背景图按采样解码、冷启动不再主线程读盘/解压、部署锁在取消时归还
- P2 修复 10/11/12/13:向导占用端口不再组合期读盘、精确输入非法不再静默、下载失败不再残留、恢复纳入 backupBusy
- P2 修复 7/8/9/22:SAF 授权不再自毁、更新状态机有出口、更新下载脱离页面作用域、去掉嵌套点击
- P2 修复 4/5/6/21:插件页不支持核心禁用、搜索 facet 不再写死 paper、MC 版本过滤不回退、搜索乱序与错误槽位
- P2 修复 1/2/3/14:备份名净化、恢复实例 ID 去重、端口范围、目录创建竞态
- P2 界面:日志导出名与失败反馈、readTail 补跳、端口不再陈旧、清空/删除补二次确认、快捷入口不再空实现、主页下拉按各自状态
- P2 核心:stdout 句柄不泄、重定向按当前跳解析、Java 目录名取实际值、Forge 安装可中止、补丁残留、备份规则
- P2:properties 保存保留注释、端口分配与写盘同临界区、EULA 锚定匹配、体积档位按舍入判定
- P2:退出处理幂等、实例删除后回收常驻状态、占用采样不再抓别人家的 java、聊天行不算名单
- 修复 P1-10/11/12/13(开关失灵、EULA 勾选被连带、控制台批量发布行数爆炸、日志分块回读拼坏边界行)
- 修复 P1-6/7/8/9(存储权限、启动检查更新、安装失败无反馈、插件模组结果串台)
- 修复 P0 数据安全三处 + P1-5(停止拦不住已排定的自动重启)
- 控制台文字精简,真机不再被截断
- 修编译:MemorySlider 的 Slider 需要 OptIn(ExperimentalMaterial3Api)
- 实例可随时改内存分配;server.properties 支持交给外部应用编辑
- docs: 新增详细的用户使用说明 docs/使用说明.md,并在 README 加文档入口
- 强制停止确认框:表述改严谨("可能会丢失",并写清 SIGTERM → 最多 5 秒)
- 强制停止前先弹确认;服务器自己停稳时该提示自动收起
- 修测试编译:反引号测试名里不能有
.(JVM 方法名非法字符) - 修编译:PROC_STATS_INTERVAL_MS 误写进类体(CI 33 秒抓到)
- 控制台占用数据:CPU 采样优化(1 秒节拍 + EMA 平滑)+ 新增整机可用内存
- 停止服务器:不再自动强杀,改成「停止中再点一次 = 强制停止」
- 控制台:占用数据读错了进程(CPU 偏低 / 内存 0.00 GB)
- 控制台:修掉采样轮询的初始化顺序坑(CI 抓到 12 个 UI 测试失败)
- 控制台:占用数据一直「采样中」—— 采样循环从来没被启动过
- CI:加一条「只编译 APK、不发版」的 workflow
- 控制台:占用数据独立成行,行数不再被挤压
- 更新 README.md
- 从仓库移除 .zcodeignore(文件保留在本机)
- 从仓库移除 .zcodeignore(文件保留在本机)
- 备份删除加二次确认(Bug 清单 P0-1)
- 服务端强制结束后仍显示"运行中":加状态自愈
v0.3.6
[0.3.6] - 2026-09-27
Fixed
- 控制台从来不显示 CPU / 内存占用(真机反馈:"控制台仍然没显示占用数据")。
根因:findServerPid要求/proc/<pid>/cmdline里同时出现java与实例目录名,
但 proot 会把子进程 cmdline 里的路径改写成/mnt/...,实例目录名根本不出现,
于是永远匹配不到、pid = null,那行数据整块不渲染 —— 从来没在任何 proot 实例上工作过。- 改用「本进程的后代里的 java」来认(app → proot → java,父子关系不会被 proot
改写):顺/proc/<pid>/stat的 ppid 往上爬,最多 16 层;优先取带实例目录名的,
否则取 RSS 最大的那个。 - 服务器运行中就一定显示这一行,取不到时显示「CPU 采样中…」而不是整行消失 ——
"采样没出来"和"功能没有"长得完全不一样,这次就是因此被误判的。 - 顺带印证:同一份真机日志里 Forge 自己也在报
File not found or not readable: /proc/stat—— proot 下整机/proc/stat确实读不到,按 pid 读才是对的路子。
- 改用「本进程的后代里的 java」来认(app → proot → java,父子关系不会被 proot
- 更新下载过程点取消没反应(真机反馈)。两个原因叠在一起:
- 界面毫无反馈:点「取消下载」只把一个标志置真,状态行 / 按钮 / 进度条全都不变,
要等下载循环跑回上一次检查点才收手 —— 观感就是点了没用。现在点下去立刻
把状态行换成「正在取消下载…」、按钮变「正在取消…」并禁用;点弹窗外部也走同一条路径。 - 有几段完全不检查取消标志:取补丁元数据那段超时是 20 秒、探测下载源也有网络等待、
补丁拼装(纯本地 1~3 秒)同样没有回调 —— 这几段里点取消最长要等 20 秒才有反应。
现在每段网络调用前后都查一次;拼装完成后也查一次并删掉拼好的文件,
避免"用户已经取消了,包还是被装上"。
- 界面毫无反馈:点「取消下载」只把一个标志置真,状态行 / 按钮 / 进度条全都不变,
- 设置页的检查更新不再显示 changelog(真机要求):只提示"发现新版本 x.y.z,
点「下载并安装」开始更新"。更新说明仍在应用启动时的更新弹窗里。 - 服务器被自己的看门狗处决:真机把 Forge 装好后服务器成功启动
(Done (238.869s)!),随后日志出现
A single server tick took 73.67 seconds→Considering it to be crashed, server will forcibly shutdown。- 根因一:
ServerProperties的默认表里没有max-tick-time,回落到 MC 默认
60000ms —— 手机上单 tick 超 60 秒太容易了。现默认-1(MC 官方语义禁用),
并给已有实例补写该键(旧实例的server.properties是首次启动后 MC 自己生成的)。
这与既有的pause-when-empty-seconds是同一类问题:手机上的"慢"不等于"死"。 - 根因二(需要用户自己调):JVM
-Xmx3072M,而设备已吃掉 6GB 交换分区
(Swap total 16384MB / used 6187.84MB),tick 卡在碰撞检测上是被换页拖住。
建议把实例内存降到 2048MB 或更低。
- 根因一:
Added
- Forge / NeoForge 安装时显示实时进度(真机反馈:"卡在这",停在
Loading patches file: .../server.lzma)。查明不是卡死:那一步是 binarypatcher
对 ~50MB 的 srg.jar 做 LZMA 解压 + 二进制差分,全程一行输出都没有,手机上要跑几分钟
(真机实测 10 分钟)。- 安装期间每 0.5 秒刷新一行:
> 安装中 10 分 8 秒 · 已写出 4.6 MB(打补丁中,无输出属正常)
—— "已写出多少 MB"读的是输出 jar 的实际大小,是真实的解压/打补丁进度。 - 用
logReplace原地替换一行,不会把控制台刷爆;且只在安装器静默超过 2 秒时
才占那一行,否则会把安装器正在输出的正常日志顶掉。 - 进度只扫
libraries/net/minecraftforge/forge/这个小目录(限制深度):每 0.5 秒
全量 walk 一次外部存储(FUSE)会把主线程拖垮。 - 安装完成时一并报出总耗时。
- 安装期间每 0.5 秒刷新一行:
- 玩家管理里点在线玩家名字,直接填进「玩家名」输入栏:原来在线列表是一整行纯文本,
名字点不动,要 op / kick 谁就得手输(中文名、大小写、下划线都容易打错)。
现在每个名字一个可点标签(自动换行),点一下即填入,接着点「设为 OP」/「踢出」即可。
Changed
- 实例详情去掉顶部的 运行 / 配置 / 世界 / 附加 标签栏(真机要求):四个分区按顺序
铺在同一页,仍然是一个Column+verticalScroll(确认过不是LazyColumn,
叠起来不会触发嵌套滚动崩溃)。连带清掉tab状态、PrimaryTabRow、
per-tab 的rememberSaveableStateHolder+SaveableStateProvider(没有标签栏就没有
"离开组合"这回事,页内rememberSaveable自然保住)、切页回顶的LaunchedEffect,
以及随之变成死代码的DetailTab枚举与两个 import。
v0.3.5
完整安装包:Kaze-SLauncher-v0.3.5-arm64-v8a.apk(arm64-v8a,推荐)
或 Kaze-SLauncher-v0.3.5-armeabi-v7a-experimental.apk(armeabi-v7a,实验性)
增量补丁(设置 → 更新方式 → 增量补丁):约 1.39 MB,只下变化的部分。
补丁要用你当前已装的 v0.3.4 当基线,拼装后会校验 sha256 与签名。
Added
- 设置里可选「更新方式」:完整安装包 / 增量补丁,默认完整安装包。
更新方式新增在设置页的更新分区(自动更新 / 更新通道 / 更新方式 / 检查更新)。- 默认整包是刻意的:补丁要拿本机已装 APK 当基线拼装,虽然每道都校验
(baseSha256 / targetSha256 / 签名),但比"直接下完整包"多一环;
让用户显式选"省流量"更稳妥。 - 只影响下载:
UpdateInstaller.download(..., allowPatch = false)时跳过补丁,
直接下整包;补丁失败本来就会自动回退整包,这条路径不变。
Changed
- 设置页的「检查更新」不再显示 changelog:只提示"发现新版本 x.y.z,
点「下载并安装」开始更新"。更新说明仍在应用启动时的更新弹窗里,或点开 Release 页看。
Added
- Forge/NeoForge 安装时显示实时进度(真机反馈:"卡在这" —— 停在
Loading patches file: .../server.lzma)。查明不是卡死:那一步是 binarypatcher
对 ~50 MB 的 srg.jar 做 LZMA 解压 + 二进制差分,全程一行输出都没有,手机上要跑几分钟。- 安装期间每 0.5 秒刷新一行:
> 安装中 3 分 12 秒 · 已写出 41.2 MB(打补丁中,无输出属正常)
—— "已写出多少 MB"读的是输出 jar 的实际大小,是真实的解压/打补丁进度。 - 用
logReplace原地替换一行,不会把控制台刷爆;并且只在安装器静默超过 2 秒
时才占那一行,否则会把安装器正在输出的正常日志顶掉。 - 进度只扫
libraries/net/minecraftforge/forge/这个小目录(限制深度):每 0.5 秒
全量 walk 一次外部存储(FUSE)会把主线程拖垮。 - 安装完成时一并报出总耗时。
- 安装期间每 0.5 秒刷新一行:
Fixed
- 更新下载过程中"取消不了"(真机反馈)—— 两个原因叠在一起:
- 界面毫无反馈:点「取消下载」只把一个标志置真,状态行 / 按钮 / 进度条全都不变
(按钮还写着"取消下载"),要等下载循环跑回上一次检查点才收尾 —— 观感就是点了没用。
现在点下去立刻把状态行换成「正在取消下载…」、按钮变成「正在取消…」并禁用;
点弹窗外部也走同一条路径。 - 有几段完全不检查取消标志:取补丁元数据那段超时是 20 秒、探测下载源最多 8 秒、
补丁拼装(纯本地 1~3 秒)也没有取消回调 —— 这几段里点取消最长要等 20 秒才有反应。
现在每段网络调用前后都查一次;拼装完成后也查一次并删掉拼好的文件,
避免"用户已经取消了,包还是被装上去"。
- 顺带修:补丁下载的状态原本报在下载之后,整个下载期间界面停在上一条状态。
- 界面毫无反馈:点「取消下载」只把一个标志置真,状态行 / 按钮 / 进度条全都不变
v0.3.4
v0.3.4
修「更新查不到带后缀的版本」、Java 下载源按实测重排、删掉那条常驻进度条,
控制台新增服务端 CPU / 内存占用显示。
[0.3.4] — 2026-09-27
Added
- 控制台显示服务端 CPU / 内存占用(都带百分比) —— 顶部那一行现在会显示
CPU 12%(1.2 核) · 内存 1.83 GB(22%),每 2 秒刷新;服务端没跑时不占位。- 服务端是 proot 的孙进程(app → proot → java),拿不到
Process句柄,所以直接按
pid 读内核:/proc/<pid>/stat取utime+stime、/proc/<pid>/status取VmRSS,
pid 靠扫/proc/*/cmdline(同时含java与本实例目录名)认出来。 - CPU 必须两次采样求差(累计 tick 本身没有意义):
单核占用率 = Δtick / (Δt秒 × 100)。
服务端多线程,单核占用率可以超过 100%,所以同时给出"用了几个核",并把界面上的
CPU%折算成整机占用率(除以核数)—— 否则 8 核机器上的 12% 会让人以为很闲,
其实是吃满了一个核。内存百分比按设备总内存算。 - 解析细节(
/proc第 2 个字段是可能含空格与括号的进程名、字段不足、tick 倒退
等)全部拆成纯函数并单独测:ProcessStatsTest8 例。
- 服务端是 proot 的孙进程(app → proot → java),拿不到
Removed
- 删掉选择版本页那条"常驻进度条"(真机反馈:"搜索栏和『第 2 步,共 3 步』之间有个
常驻的进度条")。它在WizardProgress里,是画在步骤文字下面的
WavyLinearProgress((step-1)/total),本意是"第几步"的静态指示。- 之前已经做过两轮修补(关掉无限动画、把值算对),但一条不动的进度条在中文语境里
天然读作"有个任务卡住了" —— 怎么调都还是那个观感,所以这次直接删掉。 - 步骤文字本身已经把话说清楚(
第 2 步,共 3 步 · 选版本与资源),删掉不损失信息。 - 列表加载中、下载中那两条真进度保留不动。
- 之前已经做过两轮修补(关掉无限动画、把值算对),但一条不动的进度条在中文语境里
Changed
- Java(JDK)下载源也按实测重排(真机反馈"java下载源也是,完善一下"):
实测同一台机器、同一个 JDK17 aarch64 包(取前 4 MB):
mirrors.tuna.tsinghua.edu.cn5120 KB/s ✓ /api.adoptium.net999 KB/s ✓ /
mirrors.huaweicloud.com0 KB/s(只回 12 KB 错误页) ✗ /
github.com直连(Adoptium API 返回的 githubLink)~40 KB/s ✗ 最慢。- 清华排第一;githubLink 现在会过一遍 GitHub 加速镜像(复用更新器那套 ——
它本身就是github.com/…/releases/download/…链接);官方 API 直链保留
(不依赖文件名猜得准,最稳);实测已坏的华为挪到最后当兜底。 - 进度文案也说清候选来自哪(清华 / Adoptium 官方 / GitHub 镜像)。
- 与"更新下载源"是同一套机制:
Downloader.downloadFromSources现在按吞吐探测,
顺序只是提示,真正决定选哪个的是实测速度。
- 清华排第一;githubLink 现在会过一遍 GitHub 加速镜像(复用更新器那套 ——
v0.3.3-fix
v0.3.3-fix
三件事:更新下载快了约 100 倍、更新过程全程显示状态、Forge 启动不再卡死。
[0.3.3-fix] — 2026-09-27
Fixed
- Forge 启动时控制台到一万多行就卡死(真机反馈)—— 根因是 ViewModel 每来一行就整体
复制一遍列表:(cur + line).takeLast(N)是 O(N)/行,而 Forge 启动每秒几百上千行、
列表已有一万多条 ⇒ 每秒上千万次元素拷贝,外加同样次数的 Compose 重组合,主线程被拖死。- 改为缓冲 + 批量发布:行只往缓冲里塞(O(1)),攒够 256 行或每 80ms 发布一次。
定时发布是必须的:只按行数触发的话,服务端安静下来时最后一批永远不显示 ——
而那往往正是Done (3.2s)! For help, type help这种最关键的收尾行。 - 并且列表小于 2000 行时仍然逐行即时发布:小控制台的手感与改造前完全一致,
只有真会卡死的场景才走批量。 - 新增
CONSOLE_FLUSH_BATCH/CONSOLE_FLUSH_INTERVAL_MS/CONSOLE_EAGER_LIMIT。
- 改为缓冲 + 批量发布:行只往缓冲里塞(O(1)),攒够 256 行或每 80ms 发布一次。
Added
- 更新过程显示状态(不再只有一句"下载中"):弹窗里现在按阶段显示
「正在检查增量补丁…」→「正在探测最快的下载源…(多个镜像并发测速)」→
「命中增量补丁(省 ~95% 流量),正在下载补丁…」/「下载中 12.3 MB / 30.8 MB」→
「正在拼装补丁并校验 sha256…」→「下载完成…」;失败时明确说
「所有下载源都失败了(共 N 个候选)」。- 起因:探测镜像那几秒、拼装补丁那几秒看起来都像卡死,状态行把它们区分开。
- 进度条也改了:有真实百分比时显示确定进度条,只有探测/拼装/校验这些
拿不到百分比的阶段才用不确定的滚动条(一直转圈会让人以为卡住了)。
Fixed
- 更新下载慢(真机反馈"实在是太慢了") —— 根因在源选择,不在网络:
- 应用里 13 个加速镜像实测只有 2 个能用(同一台机器、同一个 30 MB 的包):
github.ednovas.xyz890 KB/s、github.boki.moe706 KB/s,其余 11 个全部超时/失败;
而直连 github.com 只有 ~40 KB/s。 - 旧实现的探测只发 1KB Range 请求、按「连接+首字节」耗时排序 —— 直连 GitHub
首字节很快但吞吐极低,于是每次探测都是直连胜出,30 MB 的包要下十几分钟。
现在改为读最多 256 KB、按**实际吞吐(字节/毫秒)**排序
(maxByOrNull;语义从"耗时最小"变成"吞吐最大",这一处也跟着改了)。 sources()原本是listOf(直连) + 镜像:直连排第一,等于每次都先走最慢的路。
现在改成镜像优先、直连兜底。- 镜像列表按实测速度重排(最快的两个放最前),顺带去掉两个重复项。
- 实测效果:同一批资产直连要十几分钟,走镜像 40 秒(5~6 MB/s)。
- 应用里 13 个加速镜像实测只有 2 个能用(同一台机器、同一个 30 MB 的包):
- 下载源再优化一轮(同上一项,一起发):
- 补进 5 条新线路:重新用 1 MB/10s 的口径测了 18 个镜像,11 个可用
(上一轮"只有 2 个能用"是因为那次探测取了 2 MB、超时 20 秒 —— 镜像限速/抖动会让
长探测更容易超时)。现在表里可用线路从 2 条增加到 11 条,历史线路留作兜底。 ⚠️ 记一条否定结论:不要做多线程分块下载。同一镜像同一时刻整包下载实测
1 连接 7.2 MB/s、4 连接 3.5 MB/s、8 连接 2.0 MB/s —— 镜像按连接数限速,
连接越多每条越慢、总速反而下降。谁想加"6 线程 Range 下载"之前先复测这一条。
- 补进 5 条新线路:重新用 1 MB/10s 的口径测了 18 个镜像,11 个可用
v0.3.3
v0.3.3
控制台不再被刷屏冲爆:同类输出自动折叠、行数按万/百万/亿缩写、
还能往上翻更早的日志(内存之外的磁盘历史)。
[0.3.3] — 2026-09-26
Added
- 控制台可以往上翻更早的日志了(内存窗口之外的历史):列表顶部出现
「加载更早的日志」条目,点一下就从实例目录的console-output.log往前读一段
(读到头会接着读轮转的console-output.old.log)。- 实现:
ConsoleArchive从文件尾部按 256 KB 分块往回读,不把几 MB 日志整个读进内存。 - 边界处理(都是被测试抓出来的真 bug):块边界切断行时要把半行交给下一次调用
(只放在局部变量里会跨调用丢失,丢的往往正是要找的报错);提前凑够行数时偏移量
必须落在块内部(写成块起点会重复读同一块,表现为"翻不动");整块落在一行内部时
也要推进(否则死循环 → OOM)。回退偏移按字节数算 —— 中文一行 3 字节,用字符数会错位。 - 界面列表上限放宽到 6 万行(
CONSOLE_DISPLAY_MAX):否则回读的历史会被下一条
实时日志挤掉,表现成"点了加载没反应"。 - 用 LazyColumn 的 key 保持滚动位置,前插历史不会让视图跳走。
- 已知限制(已写进 KDoc):单行超过 128 KB 会被截断并标记 —— 往回读必须把逻辑行
拼完整,超长行会让中间状态膨胀(测试里直接 OOM)。真实日志行都在 1 KB 以内。 - 可回读的总量是磁盘上的约 16 MB(
console-output.log8 MB + 轮转的.old.log8 MB,
更旧的会被删)≈ 12 万行 —— 内存 5 万行 + 磁盘 16 MB,再早的确实不存在了。 - 新增
ConsoleArchiveTest6 例:分块拼回与原文逐行相同 / 每块 1 行也不漏 /
轮转前后都能读 / 读到文件头后 hasMore=false / 无日志文件安全返回 / 末行无换行符也能读。
- 实现:
v0.3.2
[0.3.2] — 2026-09-26
把增量补丁真正接进更新流程,并让 CI 自动出补丁。本版之后,从 0.3.x 升级只需下载
1.6 MB 左右(整包的 5% 上下),而不是 30 MB。
Added
- 增量补丁接入更新流程:
UpdateInstaller.download()现在先试补丁——
用本机已安装 APK(applicationInfo.sourceDir)的 sha256 去比对补丁元数据里的
baseSha256,匹配就下补丁并拼装;任何一步不成立(没有补丁资产、基线不匹配、
下载失败、拼装校验不过、签名不符)都静默回退整包。
补丁是加速手段而不是必经路径,它失败绝不能让用户更新不了。
拼装结果先比targetSha256(ApkPatchApplier内部)再比签名证书 ——
两道校验与整包路径完全一致,安全性没有放松。 - CI 自动出补丁:新增
.github/workflows/release.yml,打v*tag 时自动
跑测试 → 构建两个 ABI → 对上一个 release 的同 ABI APK 生成增量补丁 →
创建/更新 release 并上传 APK + 补丁(patch-<from>-to-<to>-<abi>.{zip,json})。
发布说明从 CHANGELOG 同版本段落抽取(tools/extract_release_notes.py,
段落缺失时写占位说明而不是留空);发布前校验 tag 与versionName一致。 UpdateChecker.pickPatchAssets():从 release 资产里挑出patch-*.json,且配对的
.zip必须存在(纯函数,5 例单测)。不按 ABI 过滤 —— 该用哪份由baseSha256
决定,名字里的 arch 只是辅助。- 更新弹窗可读化:新增
ReleaseNotes.toPlainText(),把发布正文的 Markdown
转成对话框里能读的文本(标题 → 【】、引用去>、加粗/行内代码/链接去标记、
表格 →·连接、首个含版本号的标题丢掉),正文区改成固定高度 + 可滚动、显示全文。
Fixed
- 更新弹窗把原始 Markdown 直接显示、而且被截断(真机截图实锤):原来
Text(info.body.take(400))—— 用户看到的是## v0.3.1-fix、>、**修复向**、
| 表格 |,正文还只露出开头几行。
坑:发布正文是硬换行的,**加粗**很容易跨行,逐行跑正则会漏掉那一对
(真机就是这么漏的)→ 必须先按段落合并软换行再清标记。 - 测试基建:
-Dkaze.*现在会透传给测试 JVM(此前 Gradle 的-D只作用于守护进程,
两条"吃真实产物"的用例——真实补丁互通、真实发布正文——一直在静默跳过,
绿的但什么都没验)。
v0.3.1-fix
v0.3.1-fix
这是一次修复向的版本:真机与交互上一批实打实的问题,加上应用内诊断日志、
存档目录搬到用户可见的位置。界面与功能没有大改。
⚠️ 安装前必读
- 签名与 0.3.0 相同,可直接覆盖升级(实例、存档、环境都保留)。
- 仍是两个包:64 位用
arm64-v8a,32 位老设备用armeabi-v7a(标记 experimental)。
| 你的设备 | 下载 |
|---|---|
| 64 位手机(绝大多数) | Kaze-SLauncher-v0.3.1-fix-arm64-v8a.apk |
| 32 位老设备 | Kaze-SLauncher-v0.3.1-fix-armeabi-v7a-experimental.apk |
关于增量补丁(本次先备好,客户端下版接入)
本次同时发布了 APK 增量补丁,体积只有整包的 5%~6%:
| 从 | 到 | 补丁 | 整包 | 占比 |
|---|---|---|---|---|
| v0.3.0 arm64 | v0.3.1-fix arm64 | 1.61 MB | 30.80 MB | 5.2% |
| v0.3.0 v7a | v0.3.1-fix v7a | 1.62 MB | 27.83 MB | 5.8% |
补丁的做法是"本机已装 APK 里没变的条目直接本地拷贝,变化的部分才下载",
拼装结果与官方包逐字节相同(因此 APK 签名仍然有效)。
当前版本的客户端还没有接入这条路径,所以这次更新仍会下整包;
补丁资产先发布出来,下一个版本接入后即可生效。
[0.3.1-fix] — 2026-09-26
Added
- APK 增量补丁(补丁合成更新)—— 核心已实现并验证:新版本只下"变化的那部分",
由客户端把本机已安装 APK 里没变的条目拼进来,合成与官方包逐字节相同的新包。- 实测:0.3.0 → 当前构建,103 个条目里 83 个可本地拷贝,补丁 1.61 MB = 整包的 5.2%
(整包 30.8 MB)。大头是libproot.so等原生库,逐字节不变。 - 格式
kaze-apkraw-1(参考 Operit 的 apkraw 思路、独立实现):补丁 zip 携带
meta.json(baseSha256/targetSha256/entries[])+tail.bin(中央目录 + EOCD +
APK 签名块)+ 变化条目的原始本地记录。客户端按目标包的本地记录顺序重排,
copy从本机包拷、add从补丁拷,再追加尾部。 - 为什么不用 bsdiff/hdiffpatch:不需要 NDK、不用把 30MB 读进内存;更关键的是
输出与官方包一致,所以签名仍然可验(普通差分合成的包验不过 APK 签名)。 - 三道校验:打补丁前比对本机 APK 的 sha256 ==
baseSha256(用户装了别处来的包
或版本不对就直接走整包);拼装后比对 ==targetSha256;交给安装器前仍由
UpdateInstaller比对签名证书。 - 新增
tools/make_apk_patch.py(CI 侧生成)与core/update/ApkPatchApplier.kt(客户端拼装),
格式两端严格对应;新增ApkPatchApplierTest4 例:逐字节往返、基线不匹配拒绝、
补丁被篡改被 sha256 拦下、真实生成器产出的补丁能被拼装(Python ↔ Kotlin 互通)。 - 过程中被测试抓出两个真 bug:中央目录解析用了大端读(zip 是小端,导致目录解析成空表);
本地记录长度照中央目录的 extra 算(应与本地头一致,两者可不同),
以及未处理 data descriptor(flag bit 3)。
- 实测:0.3.0 → 当前构建,103 个条目里 83 个可本地拷贝,补丁 1.61 MB = 整包的 5.2%
- 应用内「诊断日志」(设置 → 关于与许可证 → 诊断日志):应用自身的 logcat 落盘 +
环境自检合在一页,可刷新 / 分享 / 清空。- 为什么需要:Android 的 logcat 只在内存环形缓冲里(main/system/crash 各 256KiB),
应用一崩、系统一回收就没了;普通应用也没有READ_LOGS权限读不到别人的日志。
但可以读自己的 —— 以自身 UID 跑logcat --pid=<自己>,输出的正是本应用能看到的
条目,包括系统为我们打的AndroidRuntime: FATAL EXCEPTION堆栈。接进文件即可,
不用插电脑、不用 root 就能事后追溯(files/logs/app-<日期>.log,留最近 3 天、
单文件 2MB 上限,服务端刷屏也写不满存储)。
- 为什么需要:Android 的 logcat 只在内存环形缓冲里(main/system/crash 各 256KiB),
- 闪退也一定留下现场:日志采集器跑在应用进程里,进程一闪退它就跟着死,系统最后打的
FATAL EXCEPTION/ native 崩溃根本来不及落盘。两条补法:- 崩溃处理器:
Thread.setDefaultUncaughtExceptionHandler在崩溃线程上同步写出
完整堆栈(含 cause 链)再交给系统,保证 Java 崩溃有记录; - 启动时补捞:logcat 的环形缓冲在进程死后仍在内存里,所以下次启动用
logcat -d --uid=<自己> -t 800把上一段的尾巴捞回日志文件并标注「闪退现场」——
实测(MuMu 上kill -11制造真闪退)成功捞到
F DEBUG : signal 11 (SIGSEGV) ... SI_USER与周边的 crash_dump 行。 - 新增
AppLogStoreCrashTest3 例(堆栈完整性 / cause 链不丢 / 目录位置)。- 实例/存档目录默认改为手机根目录的KazeS/(如/sdcard/KazeS):存档、服务端 jar、
运行日志、备份都在用户能用文件管理器直接看到、直接拷贝的地方,不用记
Android/data/...这种路径。写共享存储需要「所有文件访问」(Android 11+)或
WRITE_EXTERNAL_STORAGE(11 以下),所以采用前会实地探测可写(写一个探针文件),
没授权时退回 app 外部私有目录,不会出现"实例建到一半才发现写不进去"。
旧默认目录里已有的实例会尝试 rename 搬过去(同分区内是瞬时的);跨挂载点搬不动时
留在原处照常可用(实例记录存的是绝对路径),设置页文案同步更新。
- 崩溃处理器:
Changed
- 「选择服务端核心」页改用各项目的官方图标(此前是几何形状的 Material 图标 + 配色,
Purpur / Spigot / Fabric / Forge / NeoForge 之间只能靠颜色区分)。现在 Vanilla 是草方块、
Paper 是纸飞机、Purpur 是紫色立方、Spigot 是水龙头、Fabric 是线轴、Forge 是铁砧。
图标统一归一化为 192×192(透明的裁掉留白后垫浅灰底,否则 Spigot 的深灰 logo 在深色卡片上看不见),
首页实例下拉里的同一组件(28dp)一并生效。来源与授权见THIRD_PARTY_NOTICES.md;
原版那张草方块是本项目自绘的像素图,不含 Mojang 贴图素材。
Fixed
- 控制台从别的页面切回来时日志停在旧位置、得自己往下翻:自动滚底用的是
LaunchedEffect(lines.size),而 lines.size 每来一行就变一次 —— 每次变化都会取消
上一个 effect,animateScrollToItem这种持续多帧的动画被取消就停在半路。平时
"人已经在底部、只差 1 行"看不出来;切页回来时差了几百行,动画永远走不完,且服务端
持续输出会让 Compose 一直不空闲。改成单个snapshotFlow收集器(滚动不被新行打断),
并按距离区分:差得少平滑滚、差得多(切页回来/恢复跟随)直接跳。
新增ConsoleFollowTest3 例钉住;旧实现下 2 例失败(ComposeTimeoutException)。 - 控制台日志上限 2000 行提到 5000 行:原先
ConsoleStream环形缓冲与 ViewModel 里
三处takeLast(2000)各写各的。现在统一为CONSOLE_MAX_LINES,行数标签到上限时会
显示「5000 行(上限)」。完整日志始终落盘在实例目录的console-output.log,
不受这个上限影响(「保存日志」与实例日志页都拿全量)。 - Forge/NeoForge「装了一半」导致永远起不来:安装完成的判据原来是"有
unix_args.txt
就算装好",而安装器是先写入口文件、后下那 60 多个依赖库的 —— 中途失败(断网、
DNS 不通、取消)会留下一个"看着装好了、库却不全"的目录,之后每次启动都跳过安装,
启动时报一长串Missing required library并退出,用户除了删实例重建没有出路。
改为以安装成功标记(.forge-installed,仅在 installer 退出码为 0 时写)为准;
有入口文件而无标记时日志会明确提示"上次安装未完成,正在补齐",installer 幂等、重跑即可自愈。 - 容器内 DNS:proot 里从来没有配过
/etc/resolv.conf,容器内联网的程序一律解析失败
(apt 静默失败、Forge 安装器直接UnknownHostException)。现在按系统当前 DNS 写一份并
在每次启动容器时绑定进去,换网络自动更新。 - 首页「启动服务端」旁的控制台快捷入口切不回主页:该入口用的是裸
navigate(),
绕过了底栏的popUpTo/saveState/restoreState语义,两种跳转混用后返回栈不可用。
已统一为同一个navigateTab。 - 版本页那个"常驻"的进度条:波浪进度条的相位在定量态也一直流动,步骤指示条因此无休止
流动;同时loadVersions/loadBuilds在任务被取消或抛异常时不复位loading,
进度条会永久停留。步骤条改为静态,加载状态用try/finally+ 请求序号复位,
并加了 45s 的界面可见超时与「重试」按钮。
Security / 健壮性(一次全仓排查后的修复)
- 实例库可能被一次半截写入清空(数据丢失):
load()解析失败静默返回空列表,
紧接着rescan()把空列表写回文件,用户的全部实例永久消失。现在解析失败先把坏文件
留档为instances.json.corrupt-<时间戳>、再退回上一次的.bak;写入改为
"临时文件 + fsync + rename"原子落盘,每次成功写入前保留上一份为.bak。 - 实例库移到内部存储:原位置
getExternalFilesDir在外部存储未挂载时返回 null,
File(null, "instances.json")会退化成相对进程 CWD 的路径(写不进去却没人知道);
Android 7–10 上其它应用也能改写它。旧文件一次性自动迁移。 - 保存失败不再静默:读取告警与保存失败分别通过
loadWarning/saveError暴露。 - 自更新 APK 增加签名校验:哈希只在 GitHub 提供
digest时才存在,而 APK 字节来自
多个第三方加速镜像 —— 镜像被控制就能返回"魔数合法、体积足够"的包。现在额外比对 APK
与本应用的签名证书,不一致或取不到签名信息一律判为不可用。 runCommand的超时分支原本永远返回不了:readJob是withContext的子协程,
proot 被杀后 guest 可能仍持有 stdout,阻塞读不会 EOF → 结构化并发一直等它,
"有界超时"变成永久挂起(Java 安装 / apt 卡住时只能杀应用)。现在超时分支按
SIGTERM → 关管道 → 强杀的顺序收尾。- JDK 判定要求虚拟机本体:
bin/java只是约 100KB 的启动器,真正的libjvm.so
最后才落盘 —— 半截安装会被认成"已就绪",服务端起不来又永远不会重装。现在必须有
libjvm.so(server 或 client)才算装好。 - 实例状态表改为原子更新:并发读改写会丢更新,表现为"已停止的实例仍显示运行中",
进而停不掉也删不掉。 - 控制台列表不再按下标回读实时 State:后台协程整体替换
lines(切实例/清空)时
可能越界崩溃,改为直接持有元素。 - 首页「重启」此前点了没反应:它直接调
startInstance,运行中会被
guardActiveStates挡掉。现在走真正的"停止 → 等状态离开 Running/Stopping → 启动"。 - 端口允许空串:清空端口再保存会写下
server-port=,端口占用统计随之漏掉该实例,
新建实例会撞端口。空串现在被拒绝。 - 幻影实例:备份/恢复用的
restore_tmp_*/restore_old_*目录里有 jar,会被目录
扫描当成实例并写进 JSON;现在扫描跳过隐藏目录与restore_前缀。 - 路由参数做 URL 编码:实例 id 在目录扫描恢复路径下等于目录名,含
#时会被当成
fragment 截断,实例详情页一闪即退。 - Lint:去掉
liquidGlassLensSafe上与实现(内部已自守卫)矛盾的@RequiresApi,
它在调用点被误报成 NewApi error。
Removed(构建内)
- 液态玻璃从软件构建中彻底移除(代码完整保留,随时可接回):
- 设置页删掉整个「液态玻璃」分区(玻璃模式 / 原生模糊 / 玻璃强度)与两个对话框,
顶部分类条从 7 个分区变回 6 个(外观 / 背景图 / 存储 / Java / 后台 / 关于); - 「主题样式」里不再出现 GLASS 选项(连那条"已封锁"提示一起删掉),
外观分区副标题里残留的「玻璃强度」也清了; AppRoot底栏的玻璃链改为编译期常量isGlass = false。这一点是关键:
原来写的是LocalAppTheme.current == GLASS,R8 无法证明它恒假,于是整条玻璃链
(glassBackdropBlur/liquidGlassLensSafe、theme/blur/与theme/shader/两个包、
LiquidGlassEffect.kt里的 shader)都因"运行时可能可达"而被打进 APK;
写成常量后编译器直接常量折叠,全部剥离。- 仓库里保留未引用的:
ui/theme/LiquidGlassEffect.kt、theme/blur/、theme/shader/、
GlassMode/glassParams/LocalGlassMode等令牌、SettingsPrefs的玻璃项、
AppThemeMode.GLASS枚举与玻璃配色分支,以及各处说明性注释。 - 实测验证:对 release dex 搜玻璃相关的字符串字面量(R8 会重命名类/方法,
类名不可靠)——cornerRadii3→0、refractionHeight4→0、refractionAmount3→0、
depthEffect3→0、KazeGlass2→0、uniform(shader 源码)6→0,全部归零。
- 设置页删掉整个「液态玻璃」分区(玻璃模式 / 原生模糊 / 玻璃强度)与两个对话框,
📦 安装包
| 架构 | 文件 | 大小 | SHA-256 |
|---|---|---|---|
| arm64-v8a | Kaze-SLauncher-v0.3.1-fix-arm64-v8a.apk |
30.8 MB | 1c2cf1aabf5d9e0f0b8c64cf76fe93e67986df3c2e030e01756b4199b19db44e |
| armeabi-v7a | Kaze-SLauncher-v0.3.1-fix-armeabi-v7a-experimental.apk |
27.8 MB | adb64c2d334bbfbd8df0eb1d78c72cff5957b712137f07770f8cb187a3f4d922 |
两个包都用发布密钥签名(CN=Kaze SLauncher),且不含 application-debuggable。
⚠️ 已知限制
- x86_64 模拟器跑不了服务端:依赖 proot,模拟器普遍用 ARM 翻译层(houdini),
执行 ARM 的libproot.so会直接段错误(实测退出码 139)。界面与下载可用,
部署环境与启动服务端需要真实 ARM 设备。 - Forge / NeoForge 的
--installServer安装链路尚未在真机上完整验证。 - 液态玻璃已从构建中移除;想要可用版本请用
liquidglassver分支。