# Keilbox
**Repository Path**: ronxia/keilbox
## Basic Information
- **Project Name**: Keilbox
- **Description**: Keilbox 是一个面向 Keil 开发场景的 Web 小工具执行器。
它本身不内置一堆功能,而是从云端拉取 HTML 代码,在本地窗口中直接运行。CRC 计算器、进制转换器、串口工具、时间戳转换……这些零散但常用的小工具,都可以作为一份 HTML 放在云端,由 Keilbox 统一加载执行。
工具在云端更新,在本地运行。
- **Primary Language**: Unknown
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2026-11-01
- **Last Updated**: 2026-11-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# KeilBox 项目 README
> Keil 工具箱:一个把自定义工具集成到 Keil µVision **Tools 菜单**的安装器 + 运行时组合方案。
> 安装器负责"注册菜单项",运行时(KeilBox.exe)负责"解析配置、下载、缓存、渲染 HTML / 启动 EXE"。
---
## 一、项目结构
| 文件 | 角色 | 说明 |
| --- | --- | --- |
| `KeilboxInstaller.cpp` | **安装器**(生成 `ToolInstaller.exe`) | 通过注册表把工具写入 Keil 的 Tools 菜单 |
| `keilbox.cpp` | **运行时**(生成 `KeilBox.exe`) | 被 Tools 菜单调起,按 `run.txt` 配置下载/缓存/展示 |
| `tools.json` | **工具清单**(远端) | 安装器拉取,展示可选工具列表 |
| `run.txt` | **单工具配置**(远端/本地) | 运行时拉取,决定具体行为 |
部署时通常把 `KeilBox.exe` 与安装器放在同一目录,安装器会自动探测 `KeilBox.exe`(缺失时回退 `HtmlViewer.exe`)。
---
## 二、功能
### 2.1 安装器(ToolInstaller.exe)
- **自动定位 Keil**
- 依次查询注册表:`HKLM\SOFTWARE\Keil\Products\{MDK,C51}`(含 WOW6432Node)。
- 兜底扫描常见目录 `C:\Keil_v5`、`D:\Keil` 等。
- 再兜底从 `PATH` 环境变量里找 `UV4.exe`。
- 均失败时弹出文件夹选择框手动指定(校验 `TOOLS.INI` 或 `UV4\UV4.exe`)。
- **拉取工具清单**
- 从可编辑的 URL 下载 `tools.json`(默认托管在腾讯云 COS)。
- 极简 JSON 解析,只认字段:`name` / `desc` / `run`。
- **勾选式安装**
- ListView 复选,支持全选 / 反选。
- 同名工具自动跳过(幂等安装,可重复运行)。
- 空槽位优先复用,避免注册表项无限膨胀。
- **菜单落点**
- 写入 `HKCU\Software\Keil\µVision5\ToolM`(并兼容乱码键名 `礦ision5` 与 `µVision4`)。
- 每工具写入:`Mtx{n}`(名称)、`Mex{n}`(程序路径)、`Mag{n}`(参数)、`Mid{n}`(工作目录)、`Mfg{n}`(标志)。
- 程序路径固定为同目录的 `KeilBox.exe`,参数为 `run` 字段(通常是 `run.txt` 的 URL)。
- **健壮性**
- 对 `run` 结尾做启发式检查(`.txt/.exe/.bat/.cmd/.html/.htm`),异常仅提示、不阻断。
- 全部已安装 / 部分跳过 / 全部成功均有对应提示。
### 2.2 运行时(KeilBox.exe)
**命令行**
```
KeilBox.exe <目标>
<目标> 可以是:
- https://.../xxx.txt → 在线 run.txt(远程配置 + 下载缓存)
- C:\path\xxx.txt → 本地 run.txt
- https://.../page → 直接 WebView 打开
- C:\path\xxx.html/.htm → 本地 HTML(UTF-8 规范化 + base 注入后加载)
- C:\path\xxx.exe → 直接启动
--current <文件> → 供页面 JS 通过 bridge 读写的"当前文件"
--local <目录> → 以虚拟域名 https://appassets.example/ 托管本地目录
其余参数 → 透传给被启动的 EXE
```
**run.txt 格式**
```ini
cnname: 我的工具
ver: 1.2.3
url: https://example.com/tool-1.2.3.zip
run: index.html ; 相对缓存目录;或 xxx.exe
ico: favicon.ico ; 可选,相对缓存目录或绝对路径
win: 1000,700 ; 可选,初始窗口大小
```
- 支持 `:` 或 `=` 分隔,忽略 `#;[]` 开头的行,字段名大小写不敏感。
- 必填:`ver` / `url` / `run`。
**缓存策略(关键)**
```
%LOCALAPPDATA%\KeilBox\cache\<工具名>\
<工具名>.txt ← 根副本(最新一次成功运行的内容)
<版本号>\
run.txt ← 版本级副本
...解压后的全部文件...
```
- 以 `run.txt` 的**原始字节**做全文比较,完全一致才算命中缓存。
- 命中 → 直接用;未命中 → 删除该版本目录 → 重新下载 ZIP → 解压。
- 网络失败 / 解析失败时会回退到本地缓存中**版本号最大**的一份。
- 版本号按"逐段数值"比较(`1.10 > 1.9`),不是字符串比较。
**WebView2 宿主**
- 依赖 **WebView2 Runtime**(缺失弹提示)。
- UserDataFolder:`%LOCALAPPDATA%\KeilBox\WebView2`。
- `--local
` 时通过 `SetVirtualHostNameToFolderMapping` 把本地目录映射到 `https://appassets.example/`,规避 `file://` 跨域限制。
- 本地 HTML 加载前会:BOM/GBK → UTF-8 规范化、缺失则补 ``、注入 `` 修正相对资源。
- 右键菜单:**置顶 / 刷新 / 复制页面地址 / 复制页面源码**;`Ctrl+T` 切换置顶。
- 页面内 `` 注释可覆盖初始窗口大小。
- 标题自动拼接:`名称 [v版本 · 本地缓存/在线下载]`。
**JS Bridge(页面 ↔ 宿主)**
页面通过 `window.chrome.webview.postMessage()` 发送 JSON,宿主以 JSON 回复,消息形如:
```json
{ "id": "任意", "method": "ping|readCurrent|writeCurrent|closeWindow", "params": { ... } }
```
| method | 参数 | 返回/作用 |
| --- | --- | --- |
| `ping` | — | `pong` + `version` + `currentFile` |
| `readCurrent` | — | 文件内容(自动识别 UTF-8/BOM/GBK),含 `encoding`、`bom`、`bytes` |
| `writeCurrent` | `content`, `backup`(默认 true) | 按读取时的编码回写;`backup=true` 生成 `.bak` |
| `closeWindow` | — | 关闭宿主窗口 |
安全约束:仅当页面来源是 `https://appassets.example/` 或 `file:///` 时 bridge 才启用。
---
## 三、实用指南
### 3.1 快速上手
1. 把 `KeilBox.exe`(或兼容名 `HtmlViewer.exe`)和 `ToolInstaller.exe` 放在同一目录。
2. 运行 `ToolInstaller.exe` → 确认 Keil 目录 → 点"下载列表"。
3. 勾选需要的工具 → "安装到 Keil"。
4. **完全退出并重启 Keil**,`Tools` 菜单即可看到新条目。
### 3.2 添加一个自己的工具
**方式 A:只挂一个网页/本地页(最简单)**
直接注册一个 `Mag` 指向 URL 或本地 HTML:
```
KeilBox.exe https://你的站点/工具页
KeilBox.exe C:\MyTools\panel.html
```
**方式 B:走 run.txt(可远程更新 + 缓存)**
1. 打包工具为 `tool.zip`(zip 内根目录放 `index.html` 或 `tool.exe`)。
2. 上传 zip,然后写一份 `run.txt` 传上去:
```ini
cnname: CRC 计算器
ver: 1.0.0
url: https://你的站点/crc-1.0.0.zip
run: index.html
```
3. 在 `tools.json` 的 `tools` 数组里追加:
```json
{ "name": "CRC 计算器", "desc": "16/32 位 CRC 计算", "run": "https://你的站点/crc.txt" }
```
4. 升级时:改 zip、改 `ver`、改 `url`(或保持 URL 不变只改 ver),用户下次点击即自动重下,旧版本仍可离线回退。
### 3.3 在页面里读写"当前工程文件"
安装器写入的 `Mag` 可以带上 `--current "工程文件路径"`,例如配合 Keil 的 `%f`(当前文件)宏:
页面里即可这样用:
```js
const wv = window.chrome?.webview;
function rpc(method, params = {}) {
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).slice(2);
const handler = (e) => {
const msg = e.data;
if (msg.id !== id) return;
wv.removeEventListener('message', handler);
msg.error ? reject(new Error(msg.error.message)) : resolve(msg.result);
};
wv.addEventListener('message', handler);
wv.postMessage({ id, method, params });
});
}
// 读取当前文件(自动识别编码)
const { content, encoding } = await rpc('readCurrent');
// 回写(自动生成 .bak)
await rpc('writeCurrent', { content: content + '\n// edited', backup: true });
```
### 3.4 常见问题排查
| 现象 | 排查 |
| --- | --- |
| 菜单里没出现新工具 | 是否**完全**退出 Keil 再开;检查 `HKCU\Software\Keil\µVision5\ToolM` 是否有 `Mtx*` |
| 提示"未找到 KeilBox.exe" | 运行时和安装器是否同目录;或改名为 `HtmlViewer.exe` |
| 首次运行白屏 | 是否安装 WebView2 Runtime;`%LOCALAPPDATA%\KeilBox\WebView2` 是否可写 |
| 页面资源 404 | 本地 HTML 相对路径会被 `` 修正;`--local` 模式请确保入口是 `index.html` |
| 断网时无法启动 | 是否曾有成功运行历史(有缓存即可离线);缓存目录在 `%LOCALAPPDATA%\KeilBox\cache` |
| 中文乱码 | HTML 保存为 UTF-8;run.txt 保存为 UTF-8(BOM 可有可无) |
### 3.5 手动清理
- 卸载工具:删除 `ToolM` 下对应的 `Mtx/Mex/Mag/Mid/Mfg{n}` 五件套即可。
- 清缓存:删除 `%LOCALAPPDATA%\KeilBox\cache`。
- 彻底卸载:删除上述注册表项 + 两个 `%LOCALAPPDATA%\KeilBox` 子目录。
---
## 四、扩展开发
### 4.1 编译环境
- **VS2022 / x64**,`Win32` 子系统。
- 安装器依赖:`comctl32 / winhttp / shlwapi / advapi32`。
- 运行时依赖:`winhttp / bcrypt / shlwapi / shell32 / ole32` + **WebView2LoaderStatic.lib** + **libzip**(`zip.lib`)。
- 建议启用通用控件 v6 清单以获得现代外观。
### 4.2 代码结构速览
**安装器**
```
HttpGet → WinHTTP 抓 tools.json
JsonGet/ParseTools→ 极简 JSON(只取 name/desc/run)
FindKeilDir → 注册表 → 常见路径 → PATH
WriteToolToRegistry
ChooseToolMKey 决定键路径(µVision5/4,含乱码兼容)
GetInstalledToolNames 同名判重
GetUsedIndices / PickFreeIndex 空洞优先分配索引
```
**运行时**
```
ParseRunTxtContent → run.txt 解析
HttpGetToString → 抓 run.txt / zip
ExtractZip → libzip 解压
PrepareCache → 全文比较命中 → 未命中重下
HtmlBridge → JS ↔ 宿主 RPC
ShowWebView → WebView2 宿主 + 虚拟域名 + 右键菜单
PrepareLocalHtml → 编码规范化 + 注入
RunFromTxt → 缓存就绪后分派到 HTML / EXE
```
### 4.3 常见扩展点
**① 支持更多 run.txt 字段**
在 `ParseRunTxtContent` 里加分支即可(它同时供"内存解析"和"文件解析"使用):
```cpp
else if (k == L"args") out.args = v; // 给 EXE 追加参数
```
**② 支持 tar.gz / 7z 包**
`ExtractZip` 是唯一解压入口,新增一个 `ExtractArchive`,按扩展名/魔数分派即可,其它流程(缓存、全文比较)无需改动。
**③ 新增 JS Bridge 方法**
在 `HtmlBridge::Handle` 的 `if/else if` 链里加方法,返回 JSON 字符串:
```cpp
else if (method == "getProjectPath") {
result = "{\"path\":" + MiniJson::Str(W2U(g_currentFile)) + "}";
}
```
注意:所有错误通过 `throw std::runtime_error("CODE")` 上报,会自动包装为 `{error:{code:"ERR",message:...}}`。
**④ 多环境注册表兼容**
`ChooseToolMKey()` 是唯一定位入口。若需支持新版 Keil 命名,新增探测顺序即可,无需改动写入逻辑。
**⑤ 自定义菜单图标 / 标志位**
`WriteToolToRegistry` 里 `Mfg` 目前写 `0x00000900`(0x09 = 可执行 + 独立窗口类标志的组合,具体含义随 Keil 版本略有差异)。需要不同行为时调整该 DWORD 即可。
**⑥ tools.json 增加字段**
`ParseTools` 已按 `name/desc/run` 取字段;如需 `category`、`icon` 等,加一行 `JsonGet(item, p, "category")` 并在 UI 中新增列。注意 `JsonGet` 用的是"从 `pos` 开始找 key"的线性扫描,字段顺序无关,但同一对象内不要重复 key。
**⑦ 离线优先策略**
`PrepareCache` 目前是"在线优先,失败回退"。若想"离线优先、后台更新",可改为:先检查 `verDir` 存在且 `TxtContentSame` 则直接返回,再异步下载新版覆盖。注意保持"根副本同步"这一不变量。
### 4.4 开发约定(来自现有代码)
- 所有内部字符串统一用 `std::wstring`,只在网络/文件/JSON 边界做 UTF-8 转换(`U2W/W2U`)。
- 缓存目录的"根副本 + 版本副本"双写是硬约束,改动 `PrepareCache` 时务必同时维护。
- 注入 ``/`` 时,若页面已存在同名标签则跳过,避免破坏作者原意。
- 任何写文件/写注册表的失败都必须向上返回错误信息(`std::wstring& err`),由 UI 层负责弹窗。
- 未命中重下时先删旧版本目录、再解压,失败则整体回滚删除,保证缓存不会处于半成品状态。
---
## 五、许可与致谢
- HTTP:WinHTTP(系统自带)
- 渲染:Microsoft Edge WebView2
- 解压:libzip
- 图标:`LoadImageW` 从文件读取,无需资源编译
> 使用时请确保所分发的工具内容与 `run.txt` / `tools.json` 的 URL 合法合规。