1
0

Add footer with disclaimer
All checks were successful
Build & Test WASM / build-and-test (push) Successful in 1m42s

This commit is contained in:
2026-03-16 15:24:15 +01:00
parent 583ad4e52c
commit 2b591a040d
6 changed files with 256 additions and 28 deletions

11
nixpacks.toml Normal file
View File

@@ -0,0 +1,11 @@
[phases.setup]
nixPkgs = ["go", "git"]
[phases.build]
cmds = [
"cd kobopatch-wasm && bash setup.sh",
"cd kobopatch-wasm && bash build.sh",
]
[staticAssets]
root = "web/public"

View File

@@ -274,11 +274,11 @@
function goToBuild() {
if (isRestore) {
firmwareDescription.textContent =
'will be downloaded and extracted without modifications to restore the original unpatched software:';
'will be downloaded and extracted without modifications to restore the original unpatched software.';
btnBuild.textContent = 'Restore Original Firmware';
} else {
firmwareDescription.textContent =
'will be downloaded automatically from Kobo\u2019s servers and will be patched after the download completes:';
'will be downloaded automatically from Kobo\u2019s servers and will be patched after the download completes.';
btnBuild.textContent = 'Build Patched Firmware';
}
setNavStep(3);
@@ -338,6 +338,9 @@
showStep(stepBuilding);
buildLog.textContent = '';
buildProgress.textContent = 'Starting...';
document.getElementById('build-wait-hint').textContent = isRestore
? 'Please wait while the original firmware is being downloaded and extracted...'
: 'Please wait while the patch is being applied...';
try {
if (!firmwareURL) {
@@ -348,7 +351,16 @@
const firmwareBytes = await downloadFirmware(firmwareURL);
appendLog('Firmware downloaded: ' + (firmwareBytes.length / 1024 / 1024).toFixed(1) + ' MB');
buildProgress.textContent = isRestore ? 'Extracting firmware...' : 'Applying patches...';
if (isRestore) {
buildProgress.textContent = 'Extracting KoboRoot.tgz...';
appendLog('Extracting original KoboRoot.tgz from firmware...');
const zip = await JSZip.loadAsync(firmwareBytes);
const koboRoot = zip.file('KoboRoot.tgz');
if (!koboRoot) throw new Error('KoboRoot.tgz not found in firmware zip');
resultTgz = new Uint8Array(await koboRoot.async('arraybuffer'));
appendLog('Extracted KoboRoot.tgz: ' + (resultTgz.length / 1024 / 1024).toFixed(1) + ' MB');
} else {
buildProgress.textContent = 'Applying patches...';
const configYAML = patchUI.generateConfig();
const patchFiles = patchUI.getPatchFileBytes();
@@ -362,6 +374,7 @@
});
resultTgz = result.tgz;
}
const sizeTxt = (resultTgz.length / 1024 / 1024).toFixed(1) + ' MB';
const action = isRestore ? 'Firmware extracted' : 'Patching complete';
const description = isRestore
@@ -480,4 +493,17 @@
enterManualMode();
}
});
// --- How it works dialog ---
const dialog = document.getElementById('how-it-works-dialog');
document.getElementById('btn-how-it-works').addEventListener('click', (e) => {
e.preventDefault();
dialog.showModal();
});
document.getElementById('btn-close-dialog').addEventListener('click', () => {
dialog.close();
});
dialog.addEventListener('click', (e) => {
if (e.target === dialog) dialog.close();
});
})();

View File

@@ -4,7 +4,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KoboPatch Web UI</title>
<link rel="stylesheet" href="style.css?ts=1773669670">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<link rel="stylesheet" href="style.css?ts=1773670636">
<script src="https://cdn.jsdelivr.net/npm/jszip@3/dist/jszip.min.js"></script>
</head>
<body>
@@ -109,13 +112,16 @@
<p id="firmware-auto-info">
Firmware <strong id="firmware-version-label"></strong> for
<strong id="firmware-device-label"></strong>
<span id="firmware-description">will be downloaded automatically from Kobo's servers and will be patched after the download completes:</span><br>
<code id="firmware-download-url"></code><br>
<span id="firmware-verify-notice">
<span id="firmware-description">will be downloaded automatically from Kobo's servers and will be patched after the download completes.</span>
</p>
<details class="log-details">
<summary>Firmware download URL</summary>
<code id="firmware-download-url"></code>
<p id="firmware-verify-notice" class="fallback-hint">
You can verify if this URL matches your Kobo's model on
<a href="https://help.kobo.com/hc/en-us/articles/35059171032727" target="_blank">Kobo's support page</a>. The most important bit is that "koboXX" matches, for example "kobo13" for Kobo Libra Color.
</span>
</p>
</details>
<!--
<p id="firmware-manual-info" hidden>
No automatic download available for your device.
@@ -139,14 +145,14 @@
<p id="build-progress">Starting...</p>
</div>
<pre id="build-log" class="build-log"></pre>
<p class="fallback-hint">Please wait while the patch is being applied...</p>
<p id="build-wait-hint" class="fallback-hint">Please wait while the patch is being applied...</p>
</section>
<!-- Step 5: Install -->
<section id="step-done" class="step" hidden>
<p id="build-status" class="install-summary"></p>
<div id="existing-tgz-warning" class="warning" hidden>
An existing KoboRoot.tgz file was found on your Kobo and has not been applied yet. It will be overwritten.
An <b>existing</b> KoboRoot.tgz file was found on your Kobo. This means an update has not been applied yet. If you choose to write the new file to your Kobo, the existing one will be overwritten.
</div>
<details class="log-details">
<summary>Build log</summary>
@@ -181,10 +187,75 @@
</section>
</main>
<footer class="site-footer">
<p>
<a href="#" id="btn-how-it-works">How does this work?</a><br/>
<br/>
Built on <a href="https://github.com/pgaskin/kobopatch" target="_blank">kobopatch</a> by pgaskin.
Patches and discussion on the <a href="https://www.mobileread.com/forums/forumdisplay.php?f=247" target="_blank">MobileRead forums</a>.
</p>
<p>
Source code available <a href="https://github.com/nicoverbruggen/kobopatch-webui" target="_blank">on GitHub</a>. This project is not affiliated with Rakuten Kobo Inc.
</p>
</footer>
<dialog id="how-it-works-dialog" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>How does this work?</h2>
<button id="btn-close-dialog" class="modal-close" aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<p>
KoboPatch Web UI is a fully client-side web application for applying custom
<a href="https://github.com/pgaskin/kobopatch" target="_blank">kobopatch</a> patches
to Kobo e-readers. Nothing is uploaded to a server &mdash; everything runs in your browser.
</p>
<h3>The patching process</h3>
<ol>
<li><strong>Device selection</strong> &mdash; On Chromium-based browsers (Chrome, Edge), the app can
auto-detect your Kobo via the File System Access API when connected over USB.
On other browsers, you manually select your model and firmware version.</li>
<li><strong>Patch configuration</strong> &mdash; You choose which patches to enable or disable.
Patches in the same group are mutually exclusive (radio buttons).
The patches themselves are community-contributed via the
<a href="https://www.mobileread.com/forums/forumdisplay.php?f=247" target="_blank">MobileRead forums</a>.</li>
<li><strong>Build</strong> &mdash; The correct firmware is downloaded directly from Kobo's servers
(<code>ereaderfiles.kobo.com</code>). The patcher, compiled from Go to WebAssembly, runs
inside a Web Worker so the UI stays responsive. It extracts the firmware's
<code>KoboRoot.tgz</code>, applies your selected patches as in-place byte replacements
to the ELF binaries, validates the results (ELF headers, file sizes, archive consistency),
and produces a new <code>KoboRoot.tgz</code>.</li>
<li><strong>Install</strong> &mdash; On Chromium, the patched file is written directly to the
<code>.kobo</code> folder on the device. On other browsers, you download the file
and copy it manually. After safely ejecting, the Kobo reboots and applies the update.</li>
</ol>
<h3>Restoring original software</h3>
<p>
You can also use this tool to restore the original unpatched firmware. In that case,
no patches are applied &mdash; the original <code>KoboRoot.tgz</code> is extracted
from the firmware zip as-is.
</p>
<h3>Safety</h3>
<p>
Each patched binary is validated before being included in the output: ELF magic bytes,
32-bit ARM architecture, file size (must match the original), and archive integrity are
all checked. If anything looks wrong, the build fails with an error. That said, patching
modifies system files, so you should know how to
<a href="https://help.kobo.com/hc/en-us/articles/360017605314" target="_blank">manually reset your Kobo</a>
if needed.
</p>
</div>
</div>
</dialog>
<!-- wasm_exec.js loaded by patch-worker.js inside the Web Worker -->
<script src="kobo-device.js?ts=1773669670"></script>
<script src="kobopatch.js?ts=1773669670"></script>
<script src="patch-ui.js?ts=1773669670"></script>
<script src="app.js?ts=1773669670"></script>
<script src="kobo-device.js?ts=1773670636"></script>
<script src="kobopatch.js?ts=1773670636"></script>
<script src="patch-ui.js?ts=1773670636"></script>
<script src="app.js?ts=1773670636"></script>
</body>
</html>

View File

@@ -386,4 +386,5 @@ class PatchUI {
}
return files;
}
}

View File

@@ -10,7 +10,7 @@ async function loadWasm() {
const go = new Go();
const result = await WebAssembly.instantiateStreaming(
fetch('kobopatch.wasm?ts=1773669670'),
fetch('kobopatch.wasm?ts=1773670636'),
go.importObject
);
go.run(result.instance);

View File

@@ -30,7 +30,7 @@
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
@@ -619,3 +619,122 @@ button:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Footer */
.site-footer {
max-width: 640px;
margin: 0 auto;
padding: 1.5rem 1.5rem 2rem;
border-top: 1px solid var(--border-light);
text-align: center;
font-size: 0.8rem;
color: var(--text-secondary);
}
.site-footer a {
color: var(--text-secondary);
text-decoration: underline;
}
.site-footer a:hover {
color: var(--text);
}
/* Modal dialog */
.modal {
border: none;
border-radius: 12px;
padding: 0;
max-width: 560px;
width: calc(100% - 2rem);
max-height: 80vh;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
}
.modal::backdrop {
background: rgba(0, 0, 0, 0.4);
}
.modal-content {
display: flex;
flex-direction: column;
max-height: 80vh;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border-light);
flex-shrink: 0;
}
.modal-header h2 {
margin-bottom: 0;
font-size: 1rem;
}
.modal-close {
background: none;
border: none;
font-size: 1.4rem;
color: var(--text-secondary);
cursor: pointer;
padding: 0 0.25rem;
line-height: 1;
box-shadow: none;
}
.modal-close:hover {
color: var(--text);
}
.modal-body {
padding: 1.25rem;
overflow-y: auto;
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.7;
}
.modal-body h3 {
font-size: 0.88rem;
font-weight: 600;
color: var(--text);
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
.modal-body p {
margin-bottom: 0.75rem;
}
.modal-body ol {
margin: 0 0 0.75rem 1.25rem;
}
.modal-body li {
margin-bottom: 0.5rem;
}
.modal-body a {
color: var(--primary);
text-decoration: none;
}
.modal-body a:hover {
text-decoration: underline;
}
.modal-body code {
font-size: 0.8rem;
background: #f1f5f9;
padding: 0.1rem 0.35rem;
border-radius: 3px;
}