gk6323分区表生成工具hitool烧录备份分区表
闲来无事,写了个小工具直接复制代码保存为.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; }
@media (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, .options select {
background: var(--code); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 4px 6px; font-size: 13px;
}
.options input { 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*(?)$/i);
if (!m) return null;
const num = parseFloat(m);
const unit = m.toUpperCase();
return { remaining: false, bytes: Math.round(num * (UNIT_MULT || 1)), human: raw };
}
function escapeXml(s) {
return String(s).replace(/[<>&'"]/g, 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.trim();
const flags = m.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;
}
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(, { 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>
感谢分享 有效验功能么 正需要,楼主辛苦了
感谢分享
页:
[1]
