Skip to content

Releases: 0Sakura721/Kaze-SLauncher

v0.5.0-beta.1

v0.5.0-beta.1 Pre-release
Pre-release

Choose a tag to compare

@0Sakura721 0Sakura721 released this 05 Oct 17:47

[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

Choose a tag to compare

@github-actions github-actions released this 01 Oct 14:56

[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

Choose a tag to compare

@github-actions github-actions released this 01 Oct 14:09

[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

Choose a tag to compare

@0Sakura721 0Sakura721 released this 27 Sep 04:50

[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 读才是对的路子。
  • 更新下载过程点取消没反应(真机反馈)。两个原因叠在一起:
    1. 界面毫无反馈:点「取消下载」只把一个标志置真,状态行 / 按钮 / 进度条全都不变,
      要等下载循环跑回上一次检查点才收手 —— 观感就是点了没用。现在点下去立刻
      把状态行换成「正在取消下载…」、按钮变「正在取消…」并禁用;点弹窗外部也走同一条路径。
    2. 有几段完全不检查取消标志:取补丁元数据那段超时是 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)会把主线程拖垮。
    • 安装完成时一并报出总耗时。
  • 玩家管理里点在线玩家名字,直接填进「玩家名」输入栏:原来在线列表是一整行纯文本,
    名字点不动,要 op / kick 谁就得手输(中文名、大小写、下划线都容易打错)。
    现在每个名字一个可点标签(自动换行),点一下即填入,接着点「设为 OP」/「踢出」即可。

Changed

  • 实例详情去掉顶部的 运行 / 配置 / 世界 / 附加 标签栏(真机要求):四个分区按顺序
    铺在同一页,仍然是一个 Column + verticalScroll(确认过不是 LazyColumn,
    叠起来不会触发嵌套滚动崩溃)。连带清掉 tab 状态、PrimaryTabRow、
    per-tab 的 rememberSaveableStateHolder + SaveableStateProvider(没有标签栏就没有
    "离开组合"这回事,页内 rememberSaveable 自然保住)、切页回顶的 LaunchedEffect,
    以及随之变成死代码的 DetailTab 枚举与两个 import。

v0.3.5

Choose a tag to compare

@0Sakura721 0Sakura721 released this 27 Sep 03:48

完整安装包: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)会把主线程拖垮。
    • 安装完成时一并报出总耗时。

Fixed

  • 更新下载过程中"取消不了"(真机反馈)—— 两个原因叠在一起:
    1. 界面毫无反馈:点「取消下载」只把一个标志置真,状态行 / 按钮 / 进度条全都不变
      (按钮还写着"取消下载"),要等下载循环跑回上一次检查点才收尾 —— 观感就是点了没用。
      现在点下去立刻把状态行换成「正在取消下载…」、按钮变成「正在取消…」并禁用;
      点弹窗外部也走同一条路径。
    2. 有几段完全不检查取消标志:取补丁元数据那段超时是 20 秒、探测下载源最多 8 秒、
      补丁拼装(纯本地 1~3 秒)也没有取消回调 —— 这几段里点取消最长要等 20 秒才有反应。
      现在每段网络调用前后都查一次;拼装完成后也查一次并删掉拼好的文件,
      避免"用户已经取消了,包还是被装上去"。
    • 顺带修:补丁下载的状态原本报在下载之后,整个下载期间界面停在上一条状态。

v0.3.4

Choose a tag to compare

@0Sakura721 0Sakura721 released this 27 Sep 03:15

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 倒退
      等)全部拆成纯函数并单独测:ProcessStatsTest 8 例。

Removed

  • 删掉选择版本页那条"常驻进度条"(真机反馈:"搜索栏和『第 2 步,共 3 步』之间有个
    常驻的进度条")。它在 WizardProgress 里,是画在步骤文字下面的
    WavyLinearProgress((step-1)/total),本意是"第几步"的静态指示。
    • 之前已经做过两轮修补(关掉无限动画、把值算对),但一条不动的进度条在中文语境里
      天然读作"有个任务卡住了" —— 怎么调都还是那个观感,所以这次直接删掉。
    • 步骤文字本身已经把话说清楚(第 2 步,共 3 步 · 选版本与资源),删掉不损失信息。
    • 列表加载中、下载中那两条真进度保留不动。

Changed

  • Java(JDK)下载源也按实测重排(真机反馈"java下载源也是,完善一下"):
    实测同一台机器、同一个 JDK17 aarch64 包(取前 4 MB):
    mirrors.tuna.tsinghua.edu.cn 5120 KB/s ✓ / api.adoptium.net 999 KB/s ✓ /
    mirrors.huaweicloud.com 0 KB/s(只回 12 KB 错误页) ✗ /
    github.com 直连(Adoptium API 返回的 githubLink)~40 KB/s ✗ 最慢。
    • 清华排第一;githubLink 现在会过一遍 GitHub 加速镜像(复用更新器那套 ——
      它本身就是 github.com/…/releases/download/… 链接);官方 API 直链保留
      (不依赖文件名猜得准,最稳);实测已坏的华为挪到最后当兜底。
    • 进度文案也说清候选来自哪(清华 / Adoptium 官方 / GitHub 镜像)。
    • 与"更新下载源"是同一套机制:Downloader.downloadFromSources 现在按吞吐探测,
      顺序只是提示,真正决定选哪个的是实测速度。


v0.3.3-fix

Choose a tag to compare

@0Sakura721 0Sakura721 released this 27 Sep 02:06

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。

Added

  • 更新过程显示状态(不再只有一句"下载中"):弹窗里现在按阶段显示
    「正在检查增量补丁…」→「正在探测最快的下载源…(多个镜像并发测速)」→
    「命中增量补丁(省 ~95% 流量),正在下载补丁…」/「下载中 12.3 MB / 30.8 MB」→
    「正在拼装补丁并校验 sha256…」→「下载完成…」;失败时明确说
    「所有下载源都失败了(共 N 个候选)」。
    • 起因:探测镜像那几秒、拼装补丁那几秒看起来都像卡死,状态行把它们区分开。
    • 进度条也改了:有真实百分比时显示确定进度条,只有探测/拼装/校验这些
      拿不到百分比的阶段才用不确定的滚动条(一直转圈会让人以为卡住了)。

Fixed

  • 更新下载慢(真机反馈"实在是太慢了") —— 根因在源选择,不在网络:
    • 应用里 13 个加速镜像实测只有 2 个能用(同一台机器、同一个 30 MB 的包):
      github.ednovas.xyz 890 KB/s、github.boki.moe 706 KB/s,其余 11 个全部超时/失败;
      而直连 github.com 只有 ~40 KB/s。
    • 旧实现的探测只发 1KB Range 请求、按「连接+首字节」耗时排序 —— 直连 GitHub
      首字节很快但吞吐极低,于是每次探测都是直连胜出,30 MB 的包要下十几分钟。
      现在改为读最多 256 KB、按**实际吞吐(字节/毫秒)**排序
      (maxByOrNull;语义从"耗时最小"变成"吞吐最大",这一处也跟着改了)。
    • sources() 原本是 listOf(直连) + 镜像:直连排第一,等于每次都先走最慢的路。
      现在改成镜像优先、直连兜底。
    • 镜像列表按实测速度重排(最快的两个放最前),顺带去掉两个重复项。
    • 实测效果:同一批资产直连要十几分钟,走镜像 40 秒(5~6 MB/s)。
  • 下载源再优化一轮(同上一项,一起发):
    • 补进 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 下载"之前先复测这一条。


v0.3.3

Choose a tag to compare

@0Sakura721 0Sakura721 released this 26 Sep 11:19

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.log 8 MB + 轮转的 .old.log 8 MB,
      更旧的会被删)≈ 12 万行 —— 内存 5 万行 + 磁盘 16 MB,再早的确实不存在了。
    • 新增 ConsoleArchiveTest 6 例:分块拼回与原文逐行相同 / 每块 1 行也不漏 /
      轮转前后都能读 / 读到文件头后 hasMore=false / 无日志文件安全返回 / 末行无换行符也能读。


v0.3.2

Choose a tag to compare

@github-actions github-actions released this 26 Sep 05:06

[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

Choose a tag to compare

@0Sakura721 0Sakura721 released this 26 Sep 03:50

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(客户端拼装),
      格式两端严格对应;新增 ApkPatchApplierTest 4 例:逐字节往返、基线不匹配拒绝、
      补丁被篡改被 sha256 拦下、真实生成器产出的补丁能被拼装(Python ↔ Kotlin 互通)。
    • 过程中被测试抓出两个真 bug:中央目录解析用了大端读(zip 是小端,导致目录解析成空表);
      本地记录长度照中央目录的 extra 算(应与本地头一致,两者可不同),
      以及未处理 data descriptor(flag bit 3)。
  • 应用内「诊断日志」(设置 → 关于与许可证 → 诊断日志):应用自身的 logcat 落盘 +
    环境自检合在一页,可刷新 / 分享 / 清空。
    • 为什么需要:Android 的 logcat 只在内存环形缓冲里(main/system/crash 各 256KiB),
      应用一崩、系统一回收就没了;普通应用也没有 READ_LOGS 权限读不到别人的日志。
      但可以读自己的 —— 以自身 UID 跑 logcat --pid=<自己>,输出的正是本应用能看到的
      条目,包括系统为我们打的 AndroidRuntime: FATAL EXCEPTION 堆栈。接进文件即可,
      不用插电脑、不用 root 就能事后追溯(files/logs/app-<日期>.log,留最近 3 天、
      单文件 2MB 上限,服务端刷屏也写不满存储)。
  • 闪退也一定留下现场:日志采集器跑在应用进程里,进程一闪退它就跟着死,系统最后打的
    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 行。
    • 新增 AppLogStoreCrashTest 3 例(堆栈完整性 / 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 收集器(滚动不被新行打断),
    并按距离区分:差得少平滑滚、差得多(切页回来/恢复跟随)直接跳。
    新增 ConsoleFollowTest 3 例钉住;旧实现下 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 会重命名类/方法,
      类名不可靠)——cornerRadii 3→0、refractionHeight 4→0、refractionAmount 3→0、
      depthEffect 3→0、KazeGlass 2→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 分支。