From 5d88914674a0389e293367e77d22559b2b00f27e Mon Sep 17 00:00:00 2001 From: Songlx516 Date: Tue, 15 Sep 2026 16:50:00 +0800 Subject: [PATCH] docs: rewrite HANDOVER.md for the closed-out 7z + versioning state The handover doc had grown to 349 lines through accretion: three separate 7z sections that repeated each other's facts, a full Frostpunk 2 debugging transcript that has been resolved since e0bc4a6, and a "current status" line still pointing at 00b750d. Anyone picking this up would have to read the whole thing to work out what is actually left to do. Rewritten around the questions a new maintainer actually asks: * what is the state, in one table, with the exact ELF hash * what do I run to build / test (the one-click script, plus the /usr/bin/bash caveat and the PIPESTATUS/od-encoding traps) * what are the design constraints I must not break (7z coder out_size, volstream struct layout, AesOpt.c flags, *at() on PS5) * what is left (only -mhe=on, plus real-hardware verification) History is compressed to the facts that still constrain future work, not the sequence in which they were discovered. New sections cover the version plumbing (2.2/2.3), the license/ownership audit result, and the two trap files sitting untracked in the repo root. No code changes. --- HANDOVER.md | 510 +++++++++++++++++++++++----------------------------- 1 file changed, 225 insertions(+), 285 deletions(-) diff --git a/HANDOVER.md b/HANDOVER.md index 7f578df..9022d23 100644 --- a/HANDOVER.md +++ b/HANDOVER.md @@ -1,348 +1,288 @@ # 交接文档 — ps5-web-file-manager 工作进度 -> 交接时间:2026-09-13(午) · 分支 main · 最新提交 **00b750d** · tag v1.9.1 待真机验证后打 +> 交接时间:2026-09-15 · 分支 `main` · 最新提交 **`0d036a7`** · tag `v1.9.1` 指向 `1fa2f09`(**需重打到 `0d036a7`**) > -> **当前主线任务(用户 2026-09-12 指令)**:ZIP/RAR/7z × 单卷/分卷 = 六种组合全部支持 + 密码通道补齐 + 报错信息尽量详细准确。 -> 进度:**①②③ 全部 ✅**(da565cc / 8bee84b~9b2f5a0 / 2748b38)· ④ **✅**(200b386 dispatch+识别+密码 UI)· ⑤ **✅**(4da345a 7zAES)· ⑥ **贯穿**(112f8a6 + 021c9cb 错误透传 + 文案)· **PS5 真机构建 ✅**(00b750d) +> **主线(用户 2026-09-12 指令)**:「先从 zip 分卷开始吧,然后把六种组合打齐,并把密码通道补齐,注意一些报错信息提示的时候尽量详细准确」 +> **状态:主线全部闭合。** 六种组合(ZIP/RAR/7z × 单卷/分卷)+ 密码通道(RAR 加密 + 7zAES)+ 报错详细信息,全部落地、测试全绿、PS5 ELF 构建成功。 +> **剩余**:① 推送 `0d036a7`;② tag 重打;③ PS5 真机端到端验证;④ `-mhe=on` 加密头(唯一功能缺口)。 -## 一、项目总体状态 +--- -v1.9(RAR 引擎换血)**已完成并打 tag**,待 push;v1.9.1 候选集(错误透传 + `*at()` 回退 + 两处真机 bug 修复)也已全绿。 +## 一、当前状态速览 -- ✅ Frostpunk 2 大 ZIP 真机问题**已解决**(根因 = PS5 `*at()` 族运行时损坏,e0bc4a6) -- ✅ 160GB/9.5万文件场景**静态核验通过**(限额余量充足,唯一风险是磁盘空间,见第四节) -- ✅ **ZIP 分卷已支持**(da565cc,三种命名约定,108 checks 全绿) -- ⏳ 主线进行中:六种组合(ZIP/RAR/7z × 单卷/分卷)+ 密码通道 → 见第五节 +| 维度 | 状态 | +|---|---| +| 解压引擎 | ZIP / RAR / 7z × 单卷/分卷(6 组合)+ 7zAES / RAR 加密 | +| 主机测试 | **ZIP 108 + RAR 27 + 7z 28 = 163 checks,0 失败**(MinGW gcc 16.2.0) | +| PS5 构建 | ✅ WSL prospero-clang 18.1.8,一键脚本可复现 | +| ELF 产物 | `web-file-mgr-v1.9.1.elf` · 1,017,864 B · sha256 `05952ac05fb937e8758846cbd8d495a427670e3cfb7f4020d42d4c6fe8c21631` · e_machine=0x003e | +| GitHub | `main` 已推到 `1fa2f09`;`0d036a7` 待推;`v1.9.1` tag 待重打 | +| 唯一功能缺口 | 7z `-mhe=on`(加密头) | -## 二、v1.9 已完成工作(全部已提交) +### 待用户在自己终端执行 + +```bash +cd "/c/Users/songl/Desktop/Web File Manager/ps5-web-file-manager" + +# 1) 推送本轮的 /api/version 改动 +git push origin main + +# 2) v1.9.1 重打到最新(1fa2f09 -> 0d036a7,让 tag 内含 /api/version + 新 ELF) +git tag -f -a v1.9.1 -m "v1.9.1 -- 7z + multi-volume + 7zAES + PS5 build + version API" 0d036a7 +git push -f origin v1.9.1 + +# 3) 可选:发 Release(附带 ELF) +gh release create v1.9.1 --title "v1.9.1" --notes-file .git/tag-v191.txt web-file-mgr-v1.9.1.elf +``` + +沙箱内 git 出站 HTTPS 被拦(502/403),推送只能用户侧执行。 + +--- + +## 二、本轮(2026-09-15)变更 + +### 2.1 一键构建脚本(`a54f34b` / `5229cd5`) + +| 文件 | 跑在哪 | 作用 | +|---|---|---| +| `.build/build-win.sh` | Windows Git Bash | 入口:把 WSL 脚本经 **stdin** 喂给 `wsl.exe`,透传退出码 | +| `.build/build-elf-wsl.sh` | WSL Ubuntu-22.04 | 5 阶段:环境检查 → rsync 同步 → `make all` → 验证 → 拷回 Windows | +| `.build/build-elf.sh` | WSL 内 | **仅首次搭环境用**(libmicrohttpd staging 安装 + sudo) | + +```bash +# Windows 端(注意:必须 /usr/bin/bash,裸 bash 会解析成 WSL 启动器) +cd "/c/Users/songl/Desktop/Web File Manager/ps5-web-file-manager" +/usr/bin/bash .build/build-win.sh + +# 或 WSL 内 +bash /home/song/build.sh +``` + +**两个关键实现点**(改脚本前必读): +- `wsl.exe -- bash -c '...'` 遇到含空格路径会被拆断 → 必须 `wsl.exe -- bash < script.sh`(stdin 重定向) +- `make ... | tail` 会吞掉退出码 → 用 `${PIPESTATUS[0]}`;否则编译失败还会继续跑验证,输出假成功 + +### 2.2 版本号体系统一(`f4fd464` / `1fa2f09` / `0d036a7`) + +原本版本号有**两个真相来源**,已经漂移过:`Makefile` 写 `v1.9.1`,`assets/main.js` 硬编 `"v1.9"`,UI 右下角在整个 v1.9.1 发布期都显示旧值。 + +现在收敛到 `Makefile` 一处: + +```make +VERSION_TAG ?= v1.9.1 # 可用 make VERSION_TAG=v1.9.2 临时覆盖 +BIN := web-file-mgr-$(VERSION_TAG).elf +``` + +改这一行会同时影响 **四处**:ELF 内嵌版本串、PS5 启动通知、输出文件名、UI 右下角。 | 提交 | 内容 | |---|---| -| c68c0de | T1: vendor unrar 7.20.1(opello 镜像 @97e1780)到 `third_party/unrar7/`(161 文件)。`rar.cpp` 的 `main()` 有 `#if !defined(RARDLL)` 守卫 → 编 `-DRARDLL` 即纯库;`dll.hpp` 自带 `extern "C"` | -| a0482a4 | T3+T4: `src/rar_extract.c` 引擎重写(unrar 顺序流 `RAROpenArchiveEx→RARReadHeaderEx→RARProcessFile`);修复 `normalize_name()` 清零目录标志的真 bug;Makefile 加 `.cpp` 规则(PS5=prospero-clang++/libc++,host=g++) | -| c58c145…da67bfa | T5: 真实 RAR fixtures(v6/加密/3卷)+ 测试用例,最终 **94 checks 全绿**(ZIP 70 + RAR 24) | -| 068983b…76c4ead | T7: 版本号/CHANGELOG/NOTICES + WSL 编译 4 坑修复(剔除 isnt.cpp/motw.cpp;`-x c` 链接;POSIX type shim;`__cpu_model` stub) | +| `f4fd464` | `VERSION_TAG` `v1.9` → `v1.9.1`;`.gitignore` 改白名单式(`.build/*` 全忽略 + `!` 放行 6 个脚本),`.workbuddy/` 也忽略 | +| `1fa2f09` | 输出文件名派生自 `VERSION_TAG`;`build-elf-wsl.sh` 从 Makefile 反读版本(不硬编);`build-win.sh` 每次都推 WSL 脚本(原来只在缺失时推,导致改了脚本 WSL 侧仍跑旧版) | +| `0d036a7` | **新增 `/api/version`**,前端右下角改从后端取值(见 2.3) | -- **ELF 产物**:`web-file-mgr.elf` 919,440 bytes(~897 KiB),sha256 `bb8f17e9addc6a9984f611503ca01b51f8984b773d353630da1f25bd1a28a997` -- **tag v1.9 已打,未 push**(沙箱 git 出站 HTTPS 被拦,需用户在自家 PowerShell 执行): - ``` - git push origin main v1.9 - ``` +### 2.3 UI 版本号改由后端提供(`0d036a7`) -## 三、历史问题:Frostpunk 2 大 ZIP 真机解压失败(**已解决**,e0bc4a6;下方保留完整排查链作参考) +**问题**:`assets/main.js:38` 的 `const APP_VERSION = "v1.9"` 与 Makefile 无关,必然漂移。 -### 现象 -真机 v1.9 解压报 `解压失败: 解压读写失败: PPSA16608-Frostpunk 2`(ZIPX_ERR_IO,detail=条目名)。 -源文件:`D:\BaiduNetdiskDownload\01.013 PPSA16608\PPSA16608-Frostpunk 2.zip`(12.7 GiB,1284 个条目)。 +**修法**: +- 新 `src/version.c` — `GET /api/version` → `{"ok":true,"version":"v1.9.1","titleId":"FMGR88888"}`,直接来自 Makefile 已传的 `-DVERSION_TAG` / `-DTITLE_ID` 宏,没有第二处要记得改 +- `src/filemgr.c` 路由表加一行(紧邻 `/api/space`)+ `filemgr_internal.h` 声明 + `Makefile` `COMMON_SRCS` +- 前端:字面量降级为 `APP_VERSION_FALLBACK`(先渲染,保证页脚不空),`loadVersion()` 后台刷新。**请求失败静默吞掉** —— 版本号显示错属于装饰性问题,不该弹错误 toast -### 已排除 -- ❌ 不是 RAR 回归(用户澄清这是 ZIP 包) -- ❌ 空间不足(用户确认 80GB 剩余,需 18GB) -- ❌ 引擎代码 32 位截断(`zip_extract.c` 纯流式 64 位写) +**踩坑**:新文件漏了 `#include "json_util.h"` → `strbuf_append` / `json_escape` 隐式声明报错。`space.c` 是模板,照抄时别漏。 -### ✅ 已核实的关键事实(2026-09-06 更新) -用 Python zipfile 读中央目录: -- 总计 1284 条目,压缩 11.85 GiB / 解出 18.00 GiB -- **最大单文件 5.45 GiB**:`frostpunk2/content/paks/pakchunk0-ps5.pak` -- **次大 4.32 GiB**:`pakchunk10-ps5.pak`(也 >4GiB) -- 两个 >4GiB 条目均为 **zip64** 格式,flag_bits=0x0000(无数据描述符) +--- -**结论:此包确实跨越了 4 GiB 单文件边界。但 host 大文件 e2e 已证明引擎 64 位写路径干净(见下方进度日志),嫌疑收窄到 PS5 平台特有行为**(PS5 libc 的 statvfs/fseeko/写路径差异,或目标挂载点限制)。 +## 三、功能矩阵与测试 -### ✅ 进度日志(2026-09-06 上午,接手人从这里继续) -1. **基线测试全绿**:`run-tests.sh` 94 checks(ZIP 70 + RAR 24)0 失败。`rm -rf` 改为增量清理(`find -name '*.o' -delete`),绕开沙箱 bulk-delete 守卫;正式环境无此限制,`rm -rf` 也可 -2. **新增 `tests/bigfile_e2e.c`**(f820016):独立大文件驱动,用法 `./bigfile_e2e `,链接 `.build/host-test/*.o`(排除 rar/unrar/test_*.o) -3. **修了两个 MinGW 测试 shim bug**(f820016,不影响 PS5): - - `tests/compat/sys/statvfs.h`:相对路径取盘符失败 → 先 `GetFullPathNameA` - - 同文件:`unsigned long` 在 Windows 是 32 位,250GB 空闲空间被截断成 2.3GB → `f_bavail` 按 4096 缩放 -4. **host 大文件 e2e 通过**:4.7GB zip64(单条目 big.bin 4.68GiB > 4GiB 边界)解出耗时 6.2s,`cmp` 与源文件逐字节一致 -5. **Makefile 加 `-DHAVE_FSEEKO`**:minizip 明确走 `fseeko/ftello`,不再赌 `ftello64` 回退链 -6. **错误诊断链已闭环**(前一轮已提交):引擎 `message` 含 strerror → `src/extract.c` `task_update()` → 前端 `backendErrorText()` 透传。**真机重试一次即可拿到真实 errno** -7. **真机报错特征分析**:detail = 无斜杠顶层目录名 `PPSA16608-Frostpunk 2`,与 `zip_extract.c` `open_parent_dirs()` L796/805(mkdirat/openat 目录失败,detail=rel)特征吻合 → 首个目录创建/打开失败,等待 errno +### 3.1 六种组合 + 密码通道 -### ✅ 真机 errno 已拿到 + 根因已修(2026-09-06 晚,e0bc4a6) -诊断版 ELF 真机重试返回:`cannot create directory 'PPSA16608-Frostpunk 2' (No error)` —— **mkdirat() 返回 -1 但 errno=0**。解压目标为内置存储(排除 FAT32 4GiB 假设)。 +| # | 引擎 | 单卷 | 分卷 | 密码 | +|---|---|---|---|---| +| ① | ZIP | ✅ | ✅ 三种命名约定 | ✅ | +| ② | RAR | ✅ | ✅(vendor unrar 7.20.1) | ✅ `RARSetPassword` | +| ③ | 7z | ✅ | ✅ `.7z.001` | ✅ 7zAES | -**根因判定**:PS5 SDK libc 的 `*at()` 族(mkdirat/openat/renameat/unlinkat)**能链接但运行时损坏**——返回 -1 且不设置 errno。佐证:staging 根目录的普通 `open()` 成功;小 zip(扁平结构、不建子目录)解压正常;报错 detail 恰为首个顶层目录名。 +### 3.2 测试 -**修复(e0bc4a6)**:`src/zip_extract.c` extract 阶段所有 `*at()` 调用加"失败即回退全路径调用"兼容层(从 `c->staging` 拼全路径): -- `open_parent_dirs`:mkdirat→mkdir、openat→open -- `write_entry`:openat→open、renameat→rename、unlinkat→unlink -- 所有相关报错追加 `(errno=%d)`,errno=0 不再伪装成 "No error" -- host 套件 94 checks 全绿;RAR 引擎无 `*at()` 调用不受影响 - -**待真机验证**:WSL 重跑 build-elf.sh(脚本会自动从 Windows 同步 e0bc4a6)→ 装 PS5 → 重解 Frostpunk 2。 - -### ✅ 真机验证通过 + 两个新 bug 已修(2026-09-06→07,01e27f3) -**Frostpunk 2(18GiB/1284 条目/5.45GiB zip64)真机内置存储完整解出**——e0bc4a6 实锤生效。随后用户测试 160GB 级分卷包时反馈两个问题,均已修复(commit `01e27f3`): - -1. **分卷 RAR 进度条不动**(真凶:逐条字节累计) - - 旧引擎只在 `RARProcessFile()` 整条返回后才 `bytes_done += UnpSize`——跨多卷的大文件单次调用可能耗时几十分钟,期间 UI 无任何更新 - - 修复:`rar_extract.c` 挂 `RARSetCallback(UCM_PROCESSDATA)`——unrar 每解压一块(disk 提取路径 `UnpWrite`)回调一次,逐块累加 + 节流上报。**注意:DLL 模式 EnableBreak 永假,回调返回 -1 会被忽略,取消仍是条目粒度**(要中断必须等当前条目结束) - - 回归断言:多卷 fixture `vol.part1.rar` 检查"解压中途出现 0 compressed×500/1000` 即拒整包。合法包里零填充/稀疏小文件压缩率轻松 >500,误伤 - - 本质:真实写出字节受"声明值上限 + check_space(按声明总量查真实空闲)"双重约束,小文件高压缩率无危害;ratio 只需拦截"大到会造成意外占盘"的条目 - - 修复:`zipx_limits_t` 新增 `ratio_min_bytes`(默认 1GiB,两 profile 相同);**仅 ≥1GiB 的条目才做 ratio 筛查**。报错消息带 `压缩后→解压后` 字节数 - - 测试:4MiB bomb.zip(~1026:1)现在两个 profile 都接受;把 floor 调到 1024 又恢复拒绝(分支仍活着) - -**v1.9.1 候选提交集**:4695295(错误透传) / 95578fb(host 64位) / f820016(shim+e2e) / e0bc4a6(*at 回退) / 01e27f3(本次两修) - -## 三·B、ZIP 分卷支持已完成(2026-09-12,提交 `da565cc`) - -### 交付内容 - -| 文件 | 作用 | -|---|---| -| `src/zipx_volstream.c/.h`(新) | 把**有序分卷列表**包装成单一连续 `mz_stream`(**不落盘合并**,随机 seek 可用) | -| `src/zipx_volume.c/.h`(新) | 分卷集**识别**:四种命名约定 + 连续性校验 + 精确报错 | -| `src/zip_extract.c`(改) | 加 `open_archive()`:分卷时按命名暗示的布局先试,失败再试另一种;`done:` 释放卷集 | -| `src/extract.c`(改) | 分卷集按格式分派;`remove_source_archives()` 删**所有**卷(避免孤儿卷残留) | -| `assets/main.js`(改) | `isZipSplitVolume()` 挂进 `isExtractableArchive()`,前端可对分卷触发解压 | -| `tests/make_split_fixtures.py`(新) | 纯 Python 手工重写中央目录,造**真实 per-disk 偏移**的 `.z01` 集 | -| `tests/test_zip_extract.c`(改) | 新增 `test_volumes()`:5 种入口 + 二进制逐字节校验 + 2 个残缺集报错断言 | - -### 支持的三种命名约定 - -| 约定 | 示例 | 语义 | 引擎模式 | -|---|---|---|---| -| byte split | `game.zip.001` `game.zip.002` … | 同一归档的字节切片,偏移**绝对** | `ZIPX_VOL_MODE_CONCAT` | -| part suffix | `game.part1.zip` `game.part2.zip` … | 同上 | `ZIPX_VOL_MODE_CONCAT` | -| zip split disks | `game.z01` `game.z02` … `game.zip` | 真·分盘,中央目录记「盘号+盘内偏移」 | `ZIPX_VOL_MODE_DISK` | - -**可以从任意一卷进入解压**(含最后一卷 `game.zip`、中间卷 `game.part2.zip`),引擎会在同目录反查并重组整集。 - -### 关键实现要点(改代码前必读) - -- `zipx_volstream` 结构体**首成员必须是 `mz_stream stream;`**(minizip 回调把 `void*` 强转) -- `vol_is_open()` 必须返回 `MZ_OK` / `MZ_OPEN_ERROR`(**不是 1/0**,否则 minizip 认为流没打开) -- vtbl **必须注册 `destroy`**,否则 `mz_stream_delete()` 不回调 → 泄漏 -- CONCAT 模式对 `DISK_NUMBER`/`DISK_SIZE` 属性返回 `MZ_PARAM_ERROR` → 让 minizip 不切盘,完全走我们的连续流 -- DISK 模式 `set_prop(DISK_NUMBER, -1)` 必须**切到最后一卷**(中央目录所在盘);minizip 路径见 `mz_zip.c:2252-2275` `mz_zip_entry_seek_local_header()` -- `tell()` 返回流内**绝对**位置,`seek()` 的 `offset` 是**相对当前盘起点**的 → DISK 模式要 `± prefix[disk]` - -### 精确报错文案(用户特别要求) - -``` -volume set is incomplete: '<全路径>/gap.zip.002' is missing -'broken.zip.001' is the first volume of a split archive but no other volumes ('broken.zip.002', ...) are present -``` - -### 验证 - -- `tests/run-tests.sh`(MinGW gcc 16.2.0):**ZIP 108 checks / RAR 27 checks,全 0 失败** -- `-Wall -Werror` 严格编译:`zipx_volume` / `zipx_volstream` / `zip_extract` 零警告 -- 未覆盖:7z 分卷(下一步);`.rar.001` 与 7z 分卷目前返回 `ZIPX_ERR_UNSUPPORTED` 并提示「rename the parts to 'x.part1.rar'」 - -### ⚠️ 环境坑(跑测试前必看) - -**不要写裸 `bash tests/run-tests.sh`** —— `bash` 可能解析到 `C:\Windows\System32\bash.exe`(WSL 启动器),脚本会跑进 Linux,gcc/python 变 Linux 版,报出莫名错误(`isnt.cpp: 'DWORD' does not name a type`、`/usr/lib/python3.10` 警告)。 - -正确姿势: ```bash export PATH="/c/mingw64/bin:/c/Users/songl/.workbuddy/binaries/PortableGit/versions/1.2.0/mingw64/bin:/c/Users/songl/.workbuddy/binaries/python/versions/3.13.12:/usr/bin:/bin:/c/Windows/System32:/c/Windows" cd "/c/Users/songl/Desktop/Web File Manager/ps5-web-file-manager" -/usr/bin/bash tests/run-tests.sh +/usr/bin/bash tests/run-tests.sh # ZIP 108 + RAR 27 +/usr/bin/bash tests/run-sevenz-tests.sh # 7z 28(含 KNOWN_GAPS 检查) ``` -## 三·C、7z 引擎已全部完成(2026-09-12→13,提交链 `8bee84b`→`2748b38`→`9b2f5a0`→`4da345a`→`021c9cb`→`200b386`) +⚠️ **绝不写裸 `bash`** —— 可能解析到 `C:\Windows\System32\bash.exe`(WSL 启动器),脚本跑进 Linux,gcc/python 全变 Linux 版,报莫名错误。必须 `/usr/bin/bash`。 -完整 7z 引擎 + 分卷 + 密码(7zAES)+ 主流程接入全部落地。 +`run-sevenz-tests.sh` 带 `KNOWN_GAPS` 列表(当前仅 `aeshe`),缺口修好后脚本会主动报错,防止列表腐烂。脚本**不做任何删除**(safe-delete 钩子会拦 `rm -rf`)。 -### 引擎本体(`2748b38`) +--- -| 文件 | 作用 | -|---|---| -| `src/sevenz_chain.c/.h`(新,~1600 行) | 自解析 folder 描述符 + **pull pipeline** 驱动 codec 链:`node_pull(n, dst, want, &got)`,不够就 `node_refill()` 拉上游。SDK 解码器 `SzArEx_Extract()` 想要整 folder 入内存,固实块动辄上 GiB,不可用 | -| `tests/make_sevenz_fixtures.py` | 真实 7-Zip 二进制造夹具:store/lzma/lzma2/ppmd/bcj/delta/utf8/bcj2/solidoff/bcj2off/aes/aeshe/vol.7z.001 | -| `tests/sevenz_chain_e2e.c` + `tests/chaincheck.py` | 引擎 e2e + Python liblzma 独立复现交叉验证 | +## 四、构建(PS5 ELF) -**【关键发现】每个 coder 节点的 `out_size` 必须取 `coder_unpack_sizes[index]`,绝不能取 folder 的 unpack size**:BCJ2 folder 里 MAIN 常大于 folder 最终尺寸(实测 300066 > 300000)。原代码把每个 coder 节点的 out_size 都设成 folder 的 unpack size → 静默短截。 +### 4.1 日常构建 -**SDK 两个硬限制(实测复现)**:`CSzFolder` 上限 **4 coder / 3 bond**;SDK 的 C 解码器**完全没有 7zAES coder**。所以引擎必须自己解析 folder 描述符 + 自己驱动 codec 链。 - -### 分卷(`9b2f5a0`) - -`src/sevenz_volstream.c/.h`(新)把有序分卷列表包成 SDK 的 `ISeekInStream`(7z 分卷是纯字节切片,偏移天生绝对)。结构体首成员 `mz_stream stream;`(**7z SDK 也走 ISeekInStream 路径**)。三条错误路径实测全部准确:缺件报文件名、只剩 `.001`、卷体积与首卷不一致。 - -### 7zAES 密码(`4da345a`) - -7zAES KDF:`numCyclesPower = b0 & 0x3F`;`saltSize = ((b0>>7)&1) + (b1>>4)`;`ivSize = ((b0>>6)&1) + (b1&0x0F)`;随后 salt → iv。`numCyclesPower == 0x3F` 时 key = `salt||password` 补齐/截断到 32 字节;否则 `key = SHA256(salt || password_utf16le || counter_le64)` 迭代 `1<.elf`,同时留在 WSL `/home/song/ps5-web-file-manager/`。 -用户场景「160GB / 9万文件 / 3-4 个 20GB 单文件」逐项核验结论:**全部通过,不会报错**。 +### 4.2 验证清单 -### 限额表(src/zip_extract.c L48-77,ZIP 与 RAR 共用) +```bash +ls -lh web-file-mgr-v1.9.1.elf +sha256sum web-file-mgr-v1.9.1.elf +od -An -tx2 -j18 -N2 web-file-mgr-v1.9.1.elf # 期望 3e00 +strings -a web-file-mgr-v1.9.1.elf | grep -m1 '^v1\.' +``` -| 限额字段 | default profile | large profile | 160GB/9万/20GB 场景 | +⚠️ `od -An -tx2` 打印的是**小端 short 的值**(`003e`),不是字节序(`3e00`)。脚本里比对用 `$((16#$EM))` 转数值(**62 = x86-64 ✅ / 183 = aarch64 ❌**)。 + +⚠️ **sha256 只在源码不变时可复现**。核对版本请用 `strings ... | grep ^v1.9`,比 sha256 直观且抗噪。 + +### 4.3 构建坑(已修,改 Makefile 前必读) + +`third_party/7z/AesOpt.c` 用编译器版本宏判断是否启用 AES-NI / AVX / VAES,clang 18 直接进 VAES 分支,但 prospero-clang 默认 target 是 generic x86_64 → `_mm256_aesenc_epi128` 未声明,20 报错。 + +- ❌ **不能** `filter-out AesOpt.c` —— `Aes.c` 通过 `AesGenTables` 引用 `AesCbc_Encode_HW` 等符号,会链接失败 +- ✅ **正解**:路径过滤 `SEVENZ_C_FLAGS := -maes -mavx2 -mvaes`,仅 `third_party/7z/*.c` 用。PS5 是 Zen 2,硬件全支持,运行时无差异 + +--- + +## 五、7z 引擎设计要点(改代码前必读) + +### 5.1 为什么不走 SDK 的解码器 + +LZMA SDK 26.03(public domain,已 vendor 到 `third_party/7z/`,解码子集 60 文件)有两个硬限制,**实测复现过**: + +1. **`CSzFolder` 上限 4 coder / 3 bond** —— 7-Zip 默认 `-m0=bcj2` 链 = BCJ2 + 4×LZMA2 = 5 coder,`SzAr_DecodeFolder()` 返回 `SZ_ERROR_UNSUPPORTED`。注意 `SzArEx_Open()` 用的是另一套宽松扫描器(`k_Scan_NumCoders_MAX 64`),所以**文件列表和解压尺寸仍然全对**,失败只在解压时按条目暴露 +2. **C 解码器完全没有 7zAES coder** —— `-p` 与 `-mhe=on` 全被拒 + +→ 因此引擎**自解析 folder blob + 自己驱动 codec 链**(`src/sevenz_chain.c/.h`,pull pipeline:`node_pull(n, dst, want, &got)`,不够就 `node_refill()` 拉上游)。 + +### 5.2 最关键的坑 + +**【必记】每个 coder 节点的 `out_size` 必须取 `coder_unpack_sizes[index]`,绝不能用 folder 的 unpack size。** BCJ2 folder 里 MAIN 常大于 folder 最终尺寸(实测 300066 > 300000)。用错的症状:每层 LZMA2 静默短 21 字节,只在特定包上暴露。 + +其他: +- 编译必须 `-DZ7_PPMD_SUPPORT`,否则 `7zDec.c` 直接丢掉 PPMd +- `CoderUnpackSizes` 是**扁平数组**(每条 = 对应 coder 输出流大小),**非累计**;`FoToCoderUnpackSizes[f]..[f+1]` 是该 folder 的切片 +- main coder = 第一个未被 bond 消费的 coder +- `SzArEx_Extract` 失败后会把半成品留在 block cache → 后续条目报**假 CRC**,要重置 `blockIndex` +- 造夹具用 Extra 包的 `7za.exe`(有 PPMd);**`7zr.exe` 没有 PPMd 编码器** +- 调试三件套在 `.build/`:`chainprobe.c`(摸内部图)、`chainprobe2.c -r `(强制 root 逐层二分)、`chaincheck.py`(Python liblzma 独立复现同一条链,秒判"图错"还是"循环错") + +### 5.3 7zAES KDF + +`numCyclesPower = b0 & 0x3F`;`saltSize = ((b0>>7)&1) + (b1>>4)`;`ivSize = ((b0>>6)&1) + (b1&0x0F)`,随后依次 salt → iv。 +`numCyclesPower == 0x3F` 时 key = `salt||password` 补齐/截断到 32 字节;否则 `key = SHA256(salt || password_utf16le || counter_le64)` 迭代 `1<480GiB 才弹窗让用户选;160GB 不触发,走 default 档。 +- 切 large 档的触发条件:**压缩包文件本身** >480 GiB(`assets/main.js` `LARGE_FILE_THRESHOLD_BYTES`),160GB 包走 default +- **ratio 有尺寸下限**(`ratio_min_bytes` = 1GiB):小文件高压缩率合法常见(零填充/稀疏),且写出字节受"声明上限 + `check_space()`"双重约束,无害 +- **唯一真实失败点是磁盘空间**:`check_space()` 按**解压后总量**查 `statvfs`,峰值 = `zip 体积 + 解出体积`。分卷场景"传一卷解一卷删一卷"可降峰值 +- **32 位安全**:引擎内部 size 全 `uint64_t`;minizip `mz_zip.h:34-35` 的 `compressed/uncompressed_size` 是 `int64_t` → >4GiB 不截断 +- **已知 UX 缺陷(未修)**:进度条 % 用字节(`main.js:1985`)、文字进度解压时用条目数(`main.js:2014`)、ETA 用字节速度(`task.c:129-177`)。混合大包上割裂,建议统一为字节 -### 已排除的技术风险(静态审查结论) +--- -1. **32 位溢出:无**。`uncompressed`、`bytes_total`、`bytes_done` 全为 `uint64_t`;minizip `mz_zip_entry` 的 `compressed_size`/`uncompressed_size` 是 `int64_t`(`mz_zip.h:34-35`)。20GB > 2³² 也不会截断 -2. **zip64 支持:已真机验证**。Frostpunk 2 的 5.45GiB zip64 条目真机解出成功;host 另测 4.7GiB zip64 逐字节一致 -3. **RAR 侧无 ratio 检查**(`rar_extract.c` 只有 file/total/entries 三项限额),压缩炸弹拦截是 ZIP 独有 -4. **nameset 哈希表**:初始 4096 槽,负载因子 0.75 自动翻倍;9万条目会扩容到 131072 槽,内存约 `131072×(8+1) ≈ 1.2MB`,可接受 +## 七、环境要点(新人必读) -### ⚠️ 真正会失败的唯一原因:磁盘空间 +- **PS5 是 x86-64 Zen 2**(不是 aarch64!),target triple `x86_64-sie-ps5` +- **PS5 SDK C++ runtime = LLVM libc++**(FreeBSD 系 sysroot,无 libstdc++)→ C++ 必须 `-stdlib=libc++`,链接 `-lc++ -lc++abi`(Makefile 已处理:unrar 用 prospero-clang++ 编) +- **PS5 SDK libc 的 `*at()` 族(mkdirat/openat/renameat/unlinkat)能链接但运行时损坏**:返回 -1 且 `errno=0`(2026-09-06 Frostpunk 2 真机确诊)。`zip_extract.c` 已有"*at() 失败回退全路径调用"兼容层;写新引擎代码时直接用全路径或沿用回退模式。报错要带 `(errno=%d)`,`errno=0` 时 `strerror` 会骗人 +- WSL:`export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk` 后才能 make +- `target/user/homebrew/` 由 songl(197609) 拥有,WSL 身份 song(1000) 写不进 → staging 模式(make install 到 /tmp → `sudo cp -r`) +- `//wsl$/Ubuntu-22.04/` 是 SMB 只读视图,改 WSL 文件必须走 Windows 路径 +- libmicrohttpd 必须 `--disable-https --disable-openssl` +- **minizip-ng 4.2.2 补丁(升级会丢)**:`src/mz_strm_os_posix.c` L25 后插 `#ifndef O_BINARY / #define O_BINARY 0 / #endif` +- **主机 POSIX shim(CP936 主机必需)**:`tests/posix_compat.h` 把 `lstat/stat → wfm_stat`、`opendir → _wopendir`(UTF-8 转换)、`fopen → _wfopen`。MinGW ANSI 入口看不见 UTF-8 文件名 +- 复杂 commit / tag message 用 `-F 文件`,不要 `-m` 长文本(bash quoting 会挂) -`check_space()`(`zip_extract.c:636-646`)按**声明的解压总量**查 `statvfs`,不是按压缩包体积。峰值需求 = `zip 体积 + 解出体积`。 -160GB 包 → **需 ≥320GB 剩余空间**,不足直接 `ZIPX_ERR_SPACE`。分卷场景下"传一卷解一卷删一卷"可把峰值压到 ~60GB。 +--- -### 进度/ETA 显示机制(用户问过) +## 八、唯一功能缺口 -- **后端**:`src/task.c:129-177` `task_update_eta_locked()` —— 环形采样 + 滑动窗口,取窗口内最老样本算 `(remaining/delta)×elapsed`;样本不足 fallback 到 `speed` -- **前端**:`assets/main.js:2017-2018` 渲染,`averageEta()`(L435)格式化 `task.eta` -- **已知口径不一致(UX 缺陷,未修)**:进度条百分比用**字节**(L1985),文字进度解压时用**条目数**(L2014-2015),ETA 用**字节速度**。在"9万小文件 + 几个20GB大文件"混合包上表现割裂——解大文件时条目数卡住、解小文件时 ETA 飙高。若要修,建议三项统一为字节,条目数降为副标题 +### `-mhe=on` 加密头 7z -## 五、下一步(主线:六种组合 + 密码通道) +`-mhe=on` 时整个 header(含 folder 表)也被加密,引擎必须在**解析 folder 之前**先用密码解密第二份 header,才能知道有哪些 folder / 用什么 coder。工作量约为标准 7zAES 的 2 倍(两次 AES 解密路径)。 -> 用户 2026-09-12 指令:「先从 zip 分卷开始吧,然后把六种组合打齐,并把密码通道补齐,注意一些报错信息提示的时候尽量详细准确」 -> 六种组合 = {ZIP, RAR, 7z} × {单卷, 分卷} +当前 `SZ_ERROR_UNSUPPORTED`,已在 `tests/run-sevenz-tests.sh` 的 `KNOWN_GAPS`(`aeshe`)标注 —— 缺口修好后脚本会主动报错提醒移除。 -| # | 任务 | 状态 | 说明 | -|---|---|---|---| -| ① | ZIP 分卷 | ✅ 完成 `da565cc` | 三种命名约定全支持,108 checks 全绿 | -| ② | **7z 引擎** | ✅ 完成 `2748b38` | 自解析 folder + 拉式 codec 链 + pull pipeline;10/10 夹具逐字节一致;本轮补 7zAES(`4da345a`) + facade UX(`112f8a6`) | -| ③ | 7z 分卷 | ✅ 完成 `9b2f5a0` | SDK `ISeekInStream` 包装有序卷列表,vol.7z.001 fixture 通过 | -| ④ | 六组合收口 | ✅ 完成 `200b386` | extract.c dispatch + main.js 识别 + 密码 UI;六种全部走通 | -| ⑤ | 密码通道 | ✅ 完成 `4da345a` | 加密 RAR (`RARSetPassword`) + 加密 7z (`7zAES`),前端共用 `extractPasswordAsk` 弹框 → 回写到 `task->extract_password` | -| ⑥ | 报错信息 | ✅ 完成 `112f8a6` | i18n 两文件更新(unsupported 移除「仅 ZIP/RAR」+ 新增 `err_extract_password`);7z facade 把 archive basename 写进密码错 detail;ESZ 引擎错误码全部映射到 `ZIPX_ERR_*` | -| ⓩ | PS5 真机构建 | ✅ 完成 `00b750d` | prospero-clang++ 18.1.8 出 ELF 1017864 B / e_machine=0x003e | -| — | 唯一已知缺口 | 🚧 `KNOWN_GAPS` | 7zAES 加密头(`-mhe=on`),需独立单元解密第二份头 + AES 解 pack 索引 | +### 真机端到端待验证 -### 七组合细节(关键收货) +ELF 已构建,但需装 PS5 实测: +1. ZIP / RAR / 7z 三类**分卷**真机解压 +2. **加密 7z(7zAES)** 真机解压 +3. 160GB / 9.5 万文件大 ZIP +4. 分卷 RAR 进度条实时走动 +5. UI 右下角版本号显示 `v1.9.1` -- **`src/sevenz_extract.c`(1753 行)** 是 7z 在主流程中的 facade。模型完全仿 `zip_extract.c` / `rar_extract.c`:staging 目录 + 同步写 `extracts//` + `publish` 阶段整 rename → atomic 发布。冲突策略:FAIL / OVERWRITE / MERGE 三档,目录碰撞一律递归下钻(仅叶子文件不同 → 比 diff) -- **CBC 报错链** `engine.dz.message` → `extract_set_error()` 拷贝到 `task->error` / `error_arg` → JSON 模板中的 `{arg}` 透传到 `assets/main.js` `backendErrorText()` → 用户看到的 toast 含**条目名 / errno / 字节数**。例如密码错:toast 显示「密码错误: aes.7z (folder 0 (7zAES): 7zAES decrypt failed)」 -- **冲突策略测试矩阵** 12 case 全部覆盖: - - 创建新目录 / 命中已存在文件 / OVERWRITE 替换 / MERGE 合并(含同名文件)+ 大小写差异 / dir-dir 递归 -- **限额内核** 与 ZIP / RAR 完全共用 `zipx_default_limits()` / `limits_profile()`(抽到 `src/zipx_common.c`);前端阈值 480 GiB 切 large 档 -- **进度 callback 阈值**:scan 阶段 256 entries 报一次(之前 4096,小包不更新 UI),extract 阶段每次 `node_pull()` 后 `bytes_done += got`;带 throttle,让 PS5 上 200 MHz NFC tag 写入频率不至于炸 +### 可选项(非阻塞) -### 7z 引擎设计要点(2026-09-12 实测定案) +- fsync 批量化(每 64MB/N 条刷一次)—— 9.5 万文件级可省 20–30 分钟 +- 解压失败保留 staging 支持续解(中等改动) +- 进度条 % / 文字进度 / ETA 三处口径统一为字节 -- **不要**引入 p7zip(LGPL);LZMA SDK 是 public domain,与项目许可兼容,已 vendor 到 `third_party/7z/`(解码子集,60 文件) -- 编译必须 `-DZ7_PPMD_SUPPORT`,否则 `7zDec.c` 直接丢掉 PPMd coder;第三方源码用 `-w` -- **SDK 两个硬限制**(都已在真夹具上复现,决定了引擎架构): - 1. **`CSzFolder` 上限 4 coder / 3 bond** —— 7-Zip 自己压的 `-m0=bcj2` 链是 `BCJ2 + 4×LZMA2 = 5 coder`,`SzAr_DecodeFolder()` 返回 `SZ_ERROR_UNSUPPORTED`。注意 `SzArEx_Open()` 用的是另一套宽松扫描器(`k_Scan_NumCoders_MAX 64`),所以**文件列表和解压尺寸仍然全对**,失败只在解压时按条目暴露 - 2. **C 解码器没有 7zAES coder** —— `IS_SUPPORTED_CODER()` 只认 Copy/LZMA/LZMA2/PPMd + 分支/Delta/BCJ2,所以 `-p` 与 `-mhe=on` 全被拒 -- **因此引擎自解析 folder blob(动态数组)+ 自己驱动 codec 链**,不走 `SzAr_DecodeFolder`。这样两个限制同时解掉 -- 流式是硬需求:7z 默认 solid 块可达 GB 级,`SzArEx_Extract` 那种"整块进内存"在 PS5 上必炸。目标形态是按 1MB 级分块驱动链式解码 -- 7zAES 用 vendor 的 `Aes.c`/`Sha256.c` 自己实现;参考 `CPP/7zip/Crypto/7zAes.cpp`: - - 属性:`b0 & 0x3F` = numCyclesPower;saltSize = `((b0>>7)&1) + (b1>>4)`;ivSize = `((b0>>6)&1) + (b1&0x0F)`,随后依次是 salt、iv - - KDF:`SHA-256(salt || password_utf16le || counter_le64)` 迭代 `1 << numCyclesPower` 次;`numCyclesPower == 0x3F` 时 key = `salt || password` 补齐/截断到 32 字节。之后 AES-256-CBC -- **PS5 内存**:LZMA2 字典需封顶(目标 ~32MB)并对超限归档给明确报错;BCJ2 四流额外占用 -- 造夹具用 Extra 包的 `7za.exe`(含 PPMd);**精简版 `7zr.exe` 没有 PPMd 编码器**(`-m0=ppmd` 报「参数错误」) -- 测试入口 `tests/run-sevenz-tests.sh`:带 `KNOWN_GAPS` 列表(bcj2/aes/aeshe/vol.7z.001),某个缺口一旦开始通过脚本会主动报错,防止列表腐烂 +--- -### 其他遗留(非本次主线) +## 九、仓库许可与代码归属(2026-09-15 核查) -- **WSL 重编 ELF**(`.build/build-elf.sh`)→ 装 PS5 → 真机复测: - 1. 160GB/9.5万文件大 zip 能解 - 2. 分卷 RAR 进度条实时走 - 3. **新增** ZIP / RAR / 7z 三类分卷的真机验证 - 4. **新增** 加密 7z(7zAES)的真机验证 -- 通过后打 tag **v1.9.1** 并 push(用户自家终端) -- 单独缺口(独立单元):**`mhe=on` 加密头 7z** —— engine 接受前必须解密第二份 header 才能读 folder 表,工作量约为标准 7zAES 的 2 倍(独立读两遍 AES)。当前以 `KNOWN_GAPS` 在 `tests/run-sevenz-tests.sh` 标注,缺口修复后该脚本会主动报错 -- 可选性能项:fsync 批量化(每 64MB/N 条刷一次)——160GB/9.5万文件级别可省 20-30 分钟;解压失败保留 staging 支持续解(中等改动) -- 可选 UX 修复:进度条% / 文字进度 / ETA 三处口径统一为字节(见第四节) +用户曾担心「项目源自他人代码、没有许可」——**前提不成立**: -### PS5 真机构建已闭环(2026-09-13 午,commit `00b750d`) +- 上游 `owendswang/ps5-web-file-manager` 经 GitHub API 确认 = **GPL-3.0**(78 stars,last push 2026-09-08) +- 本项目 `LICENSE`(GPL-3.0 全文)在 root commit `5cb0b76` 即存在,与上游一致 +- 授权链完整:`ps5-payload-dev/websrv`(John Törnblom, GPLv3+,其 Copyright 头仍保留在 `asset.c` / `asset.h` / `mime.h` / `websrv.h`)→ `owendswang` → 本项目 -首轮 `make all` 直接撞上 `prospero-clang 18` 编译 AesOpt.c 时**无声启用 AES-NI / AVX / VAES 路径**,但 **`__wmmintrin_aes.h`** 默认不开,导致 `_mm256_aesenc_epi128` 未声明 → 20 个错误 + `-ferror-limit=` 强退。 +代码量构成: +- 第三方 vendored **71,528 行**(unrar7 27,710 / zlib 20,106 / LZMA SDK 17,248 / minizip-ng 6,464)——重写时原样复用,零成本 +- 第一方 20,844 行 = 上游 v1.7 遗产 13,860 + 自有 6,984 +- **自有代码中 3,661 行零耦合**(`sevenz_chain` 2094 + `zipx_volume` 658 + `zipx_volstream` 494 + `sevenz_volstream` 415,只依赖 public domain / zlib)→ 可单独抽成 MIT 库 -修复路径上踩了反直觉坑: -1. **直接排除 AesOpt.c** → 编过,**链接失败 5 个未定义符号**(`AesCbc_Encode_HW / AesCtr_Code_HW_256` 等)。`Aes.c` 始终引用这些名字(函数指针 + `AesGenTables` 注册),不能简单删 -2. **最终选择**:路径过滤 `CFLAGS_7Z = -maes -mavx2 -mvaes`,仅 `third_party/7z/*.c` 用这一组 flag;zlib / minizip-ng / 项目源码不传 -3. PS5 是 Zen 2,硬件全支持;运行时无任何变化 +完整评估见 `docs/REWRITE-FEASIBILITY.md`(三路径:补合规 0.5 天 / 架构重构 12–18 天 / clean-room 重写 35–50 天)。**结论:建议补合规而非重写** —— GPL-3.0 保护 7z 引擎成果不被闭源白嫖。 -```diff -+SEVENZ_C_FLAGS := -maes -mavx2 -mvaes -+THIRD_PARTY_C_FLAGS_7Z := $(THIRD_PARTY_C_FLAGS) $(SEVENZ_C_FLAGS) - # 应用规则的 if 条件:仅当来源路径含 third_party/7z/ 时改用 _7Z -``` +--- -**产物**(`prospero-strip` 后): -``` --rwxr-xr-x 1 song song 995K Sep 13 14:01 web-file-mgr.elf - size: 1017864 bytes - sha256: 2eb045812ad9b0b03c5374c63db2dcbe745e59b2255e3af750132125e473a157 - e_machine=0x003e (x86-64 / PS5 ✓) -``` +## 十、工作区未跟踪文件 -ELF 体积比 v1.9(919,440 B)多了 **~100 KiB**(7z codec 全套 + Aes/Sha256/Lzma2Dec 等解码器),符号 gc 后无多余。同步回 Windows 路径 `C:\Users\songl\Desktop\Web File Manager\ps5-web-file-manager\web-file-mgr.elf`。 - -### 7z facade UX 修补(commit `112f8a6`) - -用户 2026-09-13 上午反馈「上次解压不了 / 进度不显示」,根因查实 + 锁定: - -| 项 | 根因 | 修复 | -|---|---|---| -| scan_entries 静默 | 4096 entries 才报,<4096 的包扫描期间 UI 没动 | 改为每 256 entries,扫描末 force-report | -| precheck_folders 静默 | 此阶段根本没回 callback | 入口强制一次「validating folders」 | -| 密码错 detail 空 | engine 返回 `ZIPX_ERR_PASSWORD` 时 detail 字段 NULL → i18n `{arg}` → 「密码错误: 」(光头)| `sevenz_ctx_t` 新增 `sevenz_path` 字段,密码错 detail 始终 = 入口文件名 | -| i18n `err_extract_unsupported` | 还停留在「仅 ZIP/RAR」 | 提示文案加 7z | -| 缺 `err_extract_password` 字段 | 7z 密码错无匹配 key | 新增(en + zh) | - -测试增量 6 项(`test_sevenz_extract.c --cases`):bytes_done 单调非减 + ≥ 2 次 callback + 末端 ≥ 90% 总数;密码错 `r.detail` 含 `aes.7z`。 - -最终矩阵:**ZIP 108 / RAR 27 / 7z 28 = 163 checks 全过**。 - -## 六、环境要点(新人必读) - -- **PS5 是 x86-64 Zen 2**,target triple `x86_64-sie-ps5`;SDK C++ runtime = LLVM libc++(无 libstdc++),C++ 必须编 `-stdlib=libc++`,链接 `-lc++ -lc++abi` -- **WSL 编译**:`/home/song/build-elf.sh`(repo 外副本;canonical 是 `.build/build-elf.sh`,改完须 cp 回 /home/song/)。make 前必须 `export PS5_PAYLOAD_SDK=/opt/ps5-payload-sdk` -- `target/user/homebrew/` song(1000) 写不进 → staging:make install 到 /tmp 再 sudo cp -- `//wsl$/Ubuntu-22.04/` 是 SMB 只读视图,改 WSL 文件必须走 Windows 路径对应文件 -- libmicrohttpd 必须 `--disable-https --disable-openssl`;别 `make clean` -- **minizip-ng 4.2.2 补丁(升级会丢)**:`src/mz_strm_os_posix.c` L25 后插 `#ifndef O_BINARY/#define O_BINARY 0/#endif` -- git push 只能用户在自家终端跑(沙箱出站 HTTPS 被拦);复杂 commit message 用 `-F 文件` -- Python `write_text` 在 Windows 写 CRLF + autocrlf=true 会整文件 diff → 先 `git config core.autocrlf false` -- ELF 验证:sha256 + `od -An -tx2 -j18 -N2` 看 e_machine=3e00 - -## 七、工作区未跟踪文件说明 - -`.build/bigzip|diag-remote-main.bat|engine-e2e|unrar-out|unrar-smoke|voltest` 均为排查/测试中间产物,可清理。 -`erssonglDesktopWeb File Managerps5-web-file-manager`(乱码文件)疑似误创建的空文件,可删。 +- `erssonglDesktopWeb File Managerps5-web-file-manager"`(2543 B,仓库根)—— 早期 shell 转义事故产物,**待用户确认后可删** +- `.build/` 下的探针/调试产物已被 `.gitignore` 白名单覆盖,不再污染 `git status` +- 仓库根历史遗留 `web-file-mgr-unpack 1.9.1.elf`(898K,名字写 1.9.1 但内嵌是 9-07 的 **v1.9**,无 7z 引擎)—— **陷阱文件,建议删除**