闲来无事,写了个小工具
直接复制代码保存为.html文件即可
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>烧录分区表生成器 (文本 → XML)</title>
- <style>
- :root {
- --bg: #0f1420;
- --panel: #171d2c;
- --panel-2: #1f2738;
- --border: #2a3346;
- --text: #e6ecf5;
- --muted: #8a98b0;
- --accent: #4f9dff;
- --accent-2: #36d399;
- --danger: #ff6b6b;
- --code: #0b0f17;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
- background: var(--bg);
- color: var(--text);
- line-height: 1.5;
- }
- header { padding: 20px 24px; border-bOTTom: 1px solid var(--border); background: linear-gradient(180deg, #141b2b, #0f1420); }
- header h1 { margin: 0 0 6px; font-size: 20px; }
- header p { margin: 0; color: var(--muted); font-size: 13px; }
- .wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 16px; max-width: 1400px; margin: 0 auto; }
- [url=home.php?mod=space&uid=3180104]@media[/url] (max-width: 920px) { .wrap { grid-template-columns: 1fr; } }
- .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px; display: flex; flex-direction: column; min-height: 200px; }
- .panel h2 { margin: 0 0 10px; font-size: 14px; color: var(--accent); letter-spacing: .3px; }
- textarea { width: 100%; flex: 1; min-height: 220px; resize: vertical; background: var(--code); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 12px; font-family: "SFMono-Regular", Consolas, Menlo, monospace; font-size: 13px; line-height: 1.5; }
- .options { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; font-size: 13px; color: var(--muted); align-items: center; }
- .options label { display: flex; align-items: center; gap: 6px; }
- .options input[type="number"], .options select {
- background: var(--code); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 4px 6px; font-size: 13px;
- }
- .options input[type="number"] { width: 96px; }
- .btns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
- button { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 8px 14px; font-size: 13px; cursor: pointer; transition: .15s; }
- button:hover { border-color: var(--accent); color: #fff; }
- button.primary { background: var(--accent); border-color: var(--accent); color: #04101f; font-weight: 600; }
- button.primary:hover { filter: brightness(1.08); }
- button.ok { background: var(--accent-2); border-color: var(--accent-2); color: #052016; font-weight: 600; }
- pre { background: var(--code); border: 1px solid var(--border); border-radius: 8px; padding: 12px; overflow: auto; flex: 1; min-height: 340px; margin: 0; font-family: "SFMono-Regular", Consolas, Menlo, monospace; font-size: 13px; white-space: pre; }
- .summary { width: 100%; border-collapse: collapse; font-size: 12.5px; }
- .summary th, .summary td { border: 1px solid var(--border); padding: 5px 8px; text-align: left; }
- .summary th { background: var(--panel-2); color: var(--muted); position: sticky; top: 0; }
- .summary td.num { text-align: right; font-variant-numeric: tabular-nums; }
- .summary .ro { color: var(--danger); font-weight: 600; }
- .table-scroll { max-height: 360px; overflow: auto; }
- .msg { font-size: 12.5px; margin-top: 10px; min-height: 16px; }
- .msg.err { color: var(--danger); }
- .msg.ok { color: var(--accent-2); }
- .msg.warn { color: #ffcf6b; }
- .full { grid-column: 1 / -1; }
- .note { color: var(--muted); font-size: 12px; margin: 6px 0 0; }
- code.inline { background: var(--code); padding: 1px 5px; border-radius: 4px; color: #cfe0ff; }
- .fmtbox { display:flex; gap:14px; flex-wrap:wrap; align-items:center; margin-bottom:4px; }
- .fmtbox strong { color: var(--text); }
- </style>
- </head>
- <body>
- <header>
- <h1>烧录分区表生成器 · 文本 → XML</h1>
- <p>输入 <code class="inline">设备:大小(名称)标志,大小(名称)标志,…</code> 形式的自定义文本,自动解析并生成 HiTool XML 分区表,支持预览、复制与下载。</p>
- </header>
- <div class="wrap">
- <!-- 输入区 -->
- <section class="panel">
- <h2>① 输入分区描述文本</h2>
- <textarea id="input" spellcheck="false" placeholder="例如:mmcblk0:1M(fastboot),512K(bootargs),-(userdata)"></textarea>
- <div class="options" id="hitoolOpts">
- <label>FlashType:
- <select id="flashType">
- <option value="emmc" selected>emmc</option>
- <option value="nand">nand</option>
- <option value="spi">spi</option>
- </select>
- </label>
- <label>FileSystem:
- <select id="fileSystem">
- <option value="none" selected>none</option>
- <option value="fat">fat</option>
- <option value="ext4">ext4</option>
- </select>
- </label>
- <label><input type="checkbox" id="selAll" checked /> 全部勾选(Sel=1)</label>
- </div>
- <div class="options">
- <label>总磁盘容量(MiB,可选):
- <input type="number" id="total" min="0" step="1" placeholder="留空自动" />
- </label>
- <label>扇区大小(字节):
- <input type="number" id="sector" min="1" step="1" value="512" />
- </label>
- </div>
- <div class="btns">
- <button class="primary" id="gen">生成 XML</button>
- <button id="example">载入示例</button>
- <button id="clear">清空</button>
- </div>
- <div class="msg" id="msg"></div>
- </section>
- <!-- 输出区 -->
- <section class="panel">
- <h2>② 生成的 XML 分区表</h2>
- <pre id="output">(点击“生成 XML”查看结果)</pre>
- <div class="btns">
- <button class="ok" id="copy">复制 XML</button>
- <button id="download">下载 partition_table.xml</button>
- </div>
- </section>
- <!-- 解析摘要 -->
- <section class="panel full">
- <h2>③ 解析结果摘要(用于核对)</h2>
- <div class="table-scroll">
- <table class="summary" id="summary">
- <thead>
- <tr>
- <th>#</th><th>名称</th><th>大小(可读)</th><th>大小(字节)</th>
- <th>起始(可读)</th><th>起始(字节)</th><th>起始(扇区)</th><th>标志</th>
- </tr>
- </thead>
- <tbody id="summaryBody"></tbody>
- </table>
- </div>
- <p class="note">说明:<code class="inline">1K = 1024 字节</code>、<code class="inline">1M = 1048576 字节</code>(二进制换算,符合嵌入式分区惯例);起始地址从 0 开始按序累加;<code class="inline">-</code> 表示占用剩余全部空间,<b>HiTool 格式下必须填写“总磁盘容量”才能算出其 Length</b>。</p>
- <p class="note">HiTool 注意:分区需按顺序逐条排列(首分区必须为 <code class="inline">fastboot</code> 且不可改名/删除),起始地址与长度需为 flash 块大小的整数倍,否则可能烧写出错。</p>
- </section>
- </div>
- <script>
- "use strict";
- const EXAMPLE = "mmcblk0:1M(fastboot),512K(bootargs),512K(bootargsbak),10M(stbid),1M(loaderdb),20M(recovery),2M(deviceinfo),8M(securestore),2M(atf),8M(baseparam),8M(pqparam),2M(dtbo),10M(logo),10M(logobak),20M(fastplay),20M(fastplaybak),20M(recoverybak),60M(boot),20M(misc),20M(trustedcore),1500M(system)ro,896M(cache),300M(vendor)ro,700M(backup),80M(private),256M(ctc),50M(keydata),-(userdata)";
- const UNIT_MULT = { "": 1, "K": 1024, "M": 1024 ** 2, "G": 1024 ** 3, "T": 1024 ** 4, "P": 1024 ** 5 };
- function parseSize(raw) {
- raw = raw.trim();
- if (raw === "-" || raw === "") return { remAIning: true, bytes: -1, human: "-" };
- const m = raw.match(/^(\d+(?:\.\d+)?)\s*([KMGTP]?)$/i);
- if (!m) return null;
- const num = parseFloat(m[1]);
- const unit = m[2].toUpperCase();
- return { remaining: false, bytes: Math.round(num * (UNIT_MULT[unit] || 1)), human: raw };
- }
- function escapeXml(s) {
- return String(s).replace(/[<>&'"]/g, c => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ }[c]));
- }
- function parseInput(text) {
- text = text.trim();
- if (!text) throw new Error("输入为空。");
- let device = "unknown", body = text;
- const colon = text.indexOf(":");
- if (colon !== -1) { device = text.slice(0, colon).trim(); body = text.slice(colon + 1).trim(); }
- const tokens = body.split(",").map(t => t.trim()).filter(Boolean);
- if (tokens.length === 0) throw new Error("未找到任何分区条目(请用逗号分隔)。");
- const partitions = [];
- tokens.forEach((tok, i) => {
- const pIdx = tok.indexOf("(");
- if (pIdx === -1) throw new Error(`第 ${i + 1} 个分区格式错误:“${tok}” 缺少 (名称)。`);
- const sizeRaw = tok.slice(0, pIdx);
- const rest = tok.slice(pIdx + 1);
- const m = rest.match(/^([^)]+)\)(.*)$/);
- if (!m) throw new Error(`第 ${i + 1} 个分区格式错误:“${tok}” 括号不匹配。`);
- const name = m[1].trim();
- const flags = m[2].trim();
- const size = parseSize(sizeRaw);
- if (!size) throw new Error(`第 ${i + 1} 个分区大小无法识别:“${sizeRaw}”。`);
- if (!name) throw new Error(`第 ${i + 1} 个分区名称为空。`);
- partitions.push({ name, sizeBytes: size.bytes, sizeHuman: size.human, remaining: size.remaining, flags });
- });
- return { device, partitions };
- }
- // 计算偏移(字节 / 扇区 / hex),并在提供总容量时算出剩余分区大小
- function computeOffsets(partitions, totalMiB, sectorSize) {
- let offset = 0, remainingHandled = false;
- const totalBytes = (totalMiB && totalMiB > 0) ? Math.round(totalMiB * 1024 * 1024) : 0;
- partitions.forEach(p => {
- p.start = offset;
- p.startSector = Math.floor(offset / sectorSize);
- p.startHex = "0x" + offset.toString(16).toUpperCase();
- if (p.remaining) {
- remainingHandled = true;
- if (totalBytes > 0 && totalBytes >= offset) { p.sizeBytes = totalBytes - offset; p.sizeHuman = toHisiSize(p.sizeBytes); }
- else p.sizeBytes = -1;
- }
- if (p.sizeBytes > 0) offset += p.sizeBytes;
- });
- return { usedBytes: offset, totalBytes, overflow: totalBytes > 0 && offset > totalBytes, remainingHandled };
- }
- // 转换为 HiTool 友好的人类可读单位(保证精确:整除 MiB→M,整除 KiB→K,否则原始字节)
- function toHisiSize(bytes) {
- if (bytes === 0) return "0";
- if (bytes < 0) return "";
- if (bytes % (1024 * 1024) === 0) return (bytes / 1048576) + "M";
- if (bytes % 1024 === 0) return (bytes / 1024) + "K";
- return String(bytes);
- }
- function formatBytes(b) {
- if (b < 1024) return b + "B";
- const units = ["K", "M", "G", "T"];
- let v = b / 1024, u = 0;
- while (v >= 1024 && u < units.length - 1) { v /= 1024; u++; }
- return (v >= 100 || v % 1 === 0 ? v.toFixed(0) : v.toFixed(1)) + units[u];
- }
- function generateHiTool(device, partitions, opts) {
- const L = [];
- L.push('<?xml version="1.0" encoding="GB2312"?>');
- L.push('<Partition_Info>');
- partitions.forEach(p => {
- const sel = opts.selAll ? "1" : "0";
- const start = toHisiSize(p.start);
- const length = p.remaining ? (p.sizeBytes > 0 ? toHisiSize(p.sizeBytes) : "") : toHisiSize(p.sizeBytes);
- // HiTool 标准字段:Sel / PartitionName / FlashType / FileSystem / Start / Length / SelectFile
- // SelectFile 自动生成为 <分区名>.img(如 fastboot.img)
- // (ro 等只读标志不属于该 schema,仅在摘要表展示,不写入 XML)
- L.push(` <Part Sel="${sel}" PartitionName="${escapeXml(p.name)}" FlashType="${opts.flashType}" FileSystem="${opts.fileSystem}" Start="${start}" Length="${length}" SelectFile="${escapeXml(p.name)}.img"/>`);
- });
- L.push('</Partition_Info>');
- return L.join("\n");
- }
- function renderSummary(partitions, info) {
- const tb = document.getElementById("summaryBody");
- tb.innerHTML = "";
- partitions.forEach((p, i) => {
- const tr = document.createElement("tr");
- const sizeB = p.sizeBytes < 0 ? "(剩余)" : p.sizeBytes.toLocaleString();
- const sizeH = p.sizeBytes < 0 ? "(剩余)" : toHisiSize(p.sizeBytes);
- const roCell = p.flags.toLowerCase() === "ro" ? `<span class="ro">ro</span>` : (escapeXml(p.flags) || "—");
- tr.innerHTML = `
- <td>${i + 1}</td>
- <td>${escapeXml(p.name)}</td>
- <td>${sizeH}</td>
- <td class="num">${sizeB}</td>
- <td>${toHisiSize(p.start)}</td>
- <td class="num">${p.start.toLocaleString()}</td>
- <td class="num">${p.startSector.toLocaleString()}</td>
- <td>${roCell}</td>`;
- tb.appendChild(tr);
- });
- const tr = document.createElement("tr");
- tr.style.fontWeight = "600";
- tr.innerHTML = `
- <td colspan="3">合计已定空间</td>
- <td class="num">${info.usedBytes.toLocaleString()}</td>
- <td colspan="3">${info.totalBytes ? "总容量 " + info.totalBytes.toLocaleString() + " 字节" : "(未设总容量)"}</td>
- <td></td>`;
- tb.appendChild(tr);
- }
- function showMsg(text, type) {
- const el = document.getElementById("msg");
- el.textContent = text;
- el.className = "msg" + (type ? " " + type : "");
- }
- function doGenerate() {
- const text = document.getElementById("input").value;
- const totalMiB = parseFloat(document.getElementById("total").value) || 0;
- const sectorSize = Math.max(1, parseInt(document.getElementById("sector").value, 10) || 512);
- try {
- const { device, partitions } = parseInput(text);
- const info = computeOffsets(partitions, totalMiB, sectorSize);
- const opts = {
- flashType: document.getElementById("flashType").value,
- fileSystem: document.getElementById("fileSystem").value,
- selAll: document.getElementById("selAll").checked
- };
- const xml = generateHiTool(device, partitions, opts);
- document.getElementById("output").textContent = xml;
- renderSummary(partitions, info);
- let note = `解析成功:${partitions.length} 个分区,已定空间 ${formatBytes(info.usedBytes)}。`;
- let type = "ok";
- if (info.remainingHandled && !info.totalBytes) {
- note += " ⚠ 末尾含 -(剩余) 分区且未填“总磁盘容量”,其 Length 留空,请补填容量后重新生成。";
- type = "warn";
- }
- if (info.totalBytes && info.overflow) {
- note += " ⚠ 已定空间超过总容量!请检查总容量或分区大小。";
- type = "err";
- }
- showMsg(note, type);
- } catch (e) {
- document.getElementById("output").textContent = "(生成失败)";
- document.getElementById("summaryBody").innerHTML = "";
- showMsg("解析错误:" + e.message, "err");
- }
- }
- document.getElementById("gen").addEventListener("click", doGenerate);
- document.getElementById("example").addEventListener("click", () => { document.getElementById("input").value = EXAMPLE; doGenerate(); });
- document.getElementById("clear").addEventListener("click", () => {
- document.getElementById("input").value = "";
- document.getElementById("output").textContent = "(点击“生成 XML”查看结果)";
- document.getElementById("summaryBody").innerHTML = "";
- showMsg("");
- });
- document.getElementById("copy").addEventListener("click", async () => {
- const txt = document.getElementById("output").textContent;
- try { await navigator.clipboard.writeText(txt); showMsg("XML 已复制到剪贴板。", "ok"); }
- catch {
- const ta = document.createElement("textarea"); ta.value = txt; document.body.appendChild(ta); ta.select();
- document.execCommand("copy"); document.body.removeChild(ta); showMsg("XML 已复制(兼容模式)。", "ok");
- }
- });
- document.getElementById("download").addEventListener("click", () => {
- const txt = document.getElementById("output").textContent;
- const blob = new Blob([txt], { type: "application/xml;charset=utf-8" });
- const url = URL.createObjectURL(blob);
- const a = document.createElement("a");
- a.href = url; a.download = "partition_table.xml";
- document.body.appendChild(a); a.click(); document.body.removeChild(a);
- URL.revokeObjectURL(url);
- });
- // 初始载入示例并生成(默认 HiTool 格式)
- document.getElementById("input").value = EXAMPLE;
- doGenerate();
- </script>
- </body>
- </html>
复制代码
partition_xml_generator.txt
(15.76 KB, 下载次数: 10)
|