WIP
All checks were successful
Build & Test WASM / build-and-test (push) Successful in 2m45s
All checks were successful
Build & Test WASM / build-and-test (push) Successful in 2m45s
This commit is contained in:
@@ -300,6 +300,11 @@
|
|||||||
(resultTgz.length / 1024).toFixed(0) + ' KB.';
|
(resultTgz.length / 1024).toFixed(0) + ' KB.';
|
||||||
writeSuccess.hidden = true;
|
writeSuccess.hidden = true;
|
||||||
|
|
||||||
|
// Copy log to done step
|
||||||
|
const doneLog = document.getElementById('done-log');
|
||||||
|
doneLog.textContent = buildLog.textContent;
|
||||||
|
doneLog.scrollTop = doneLog.scrollHeight;
|
||||||
|
|
||||||
// In manual mode, hide the "Write to Kobo" button
|
// In manual mode, hide the "Write to Kobo" button
|
||||||
btnWrite.hidden = manualMode;
|
btnWrite.hidden = manualMode;
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@
|
|||||||
<section id="step-done" class="step" hidden>
|
<section id="step-done" class="step" hidden>
|
||||||
<h2>Done!</h2>
|
<h2>Done!</h2>
|
||||||
<div id="build-status" class="status-supported"></div>
|
<div id="build-status" class="status-supported"></div>
|
||||||
|
<pre id="done-log" class="build-log"></pre>
|
||||||
<div id="build-actions">
|
<div id="build-actions">
|
||||||
<button id="btn-write" class="primary">Write to Kobo</button>
|
<button id="btn-write" class="primary">Write to Kobo</button>
|
||||||
<button id="btn-download" class="secondary">Download KoboRoot.tgz</button>
|
<button id="btn-download" class="secondary">Download KoboRoot.tgz</button>
|
||||||
|
|||||||
50
src/public/patch-worker.js
Normal file
50
src/public/patch-worker.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Web Worker for running kobopatch WASM off the main thread.
|
||||||
|
// Communicates with the main thread via postMessage.
|
||||||
|
|
||||||
|
importScripts('wasm_exec.js');
|
||||||
|
|
||||||
|
let wasmReady = false;
|
||||||
|
|
||||||
|
async function loadWasm() {
|
||||||
|
if (wasmReady) return;
|
||||||
|
|
||||||
|
const go = new Go();
|
||||||
|
const result = await WebAssembly.instantiateStreaming(
|
||||||
|
fetch('kobopatch.wasm'),
|
||||||
|
go.importObject
|
||||||
|
);
|
||||||
|
go.run(result.instance);
|
||||||
|
|
||||||
|
if (typeof globalThis.patchFirmware !== 'function') {
|
||||||
|
throw new Error('WASM module loaded but patchFirmware() not found');
|
||||||
|
}
|
||||||
|
wasmReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = async function(e) {
|
||||||
|
const { type, configYAML, firmwareZip, patchFiles } = e.data;
|
||||||
|
|
||||||
|
if (type !== 'patch') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
self.postMessage({ type: 'progress', message: 'Loading WASM patcher...' });
|
||||||
|
await loadWasm();
|
||||||
|
self.postMessage({ type: 'progress', message: 'WASM module loaded' });
|
||||||
|
|
||||||
|
self.postMessage({ type: 'progress', message: 'Applying patches...' });
|
||||||
|
|
||||||
|
const result = await globalThis.patchFirmware(configYAML, firmwareZip, patchFiles, (msg) => {
|
||||||
|
self.postMessage({ type: 'progress', message: msg });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Transfer the tgz buffer to avoid copying
|
||||||
|
const tgzBuffer = result.tgz.buffer;
|
||||||
|
self.postMessage({
|
||||||
|
type: 'done',
|
||||||
|
tgz: result.tgz,
|
||||||
|
log: result.log,
|
||||||
|
}, [tgzBuffer]);
|
||||||
|
} catch (err) {
|
||||||
|
self.postMessage({ type: 'error', message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -286,7 +286,7 @@ button:disabled {
|
|||||||
font-family: "SF Mono", "Fira Code", monospace;
|
font-family: "SF Mono", "Fira Code", monospace;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
max-height: 200px;
|
height: calc(10 * 1.5em + 1.5rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,47 +8,67 @@ kobopatch is compiled from Go to WebAssembly and runs entirely in the browser.
|
|||||||
```
|
```
|
||||||
Browser
|
Browser
|
||||||
├── index.html + CSS + JS
|
├── index.html + CSS + JS
|
||||||
├── kobopatch.wasm (Go compiled to WASM)
|
├── patch-worker.js (Web Worker for off-thread patching)
|
||||||
├── Patch YAML files (bundled as static assets)
|
├── kobopatch.wasm (Go compiled to WASM, loaded by worker)
|
||||||
└── File System Access API (read/write Kobo USB drive)
|
├── Patch config zips (in src/public/patches/)
|
||||||
|
├── Firmware auto-download (fetched from ereaderfiles.kobo.com)
|
||||||
|
└── File System Access API (read/write Kobo USB drive, Chromium only)
|
||||||
```
|
```
|
||||||
|
|
||||||
## User Flow
|
## User Flow
|
||||||
|
|
||||||
|
### Auto mode (Chromium)
|
||||||
1. Connect Kobo via USB
|
1. Connect Kobo via USB
|
||||||
2. Click "Select Kobo Drive" → browser reads `.kobo/version`
|
2. Click "Select Kobo Drive" → browser reads `.kobo/version`
|
||||||
3. App detects model + firmware version from serial prefix
|
3. App detects model + firmware version from serial prefix
|
||||||
4. User provides firmware zip file (file picker / drag-and-drop)
|
4. App shows available patches with toggles, grouped by target binary
|
||||||
5. App shows available patches with toggles, grouped by target binary
|
5. User configures patches (enable/disable)
|
||||||
6. User configures patches (enable/disable)
|
6. Click "Build" → firmware downloaded from Kobo CDN → WASM patches in Web Worker
|
||||||
7. Click "Build" → WASM runs kobopatch in-browser
|
7. App writes resulting `KoboRoot.tgz` to `.kobo/` on device (or download)
|
||||||
8. App writes resulting `KoboRoot.tgz` to `.kobo/` on device
|
8. User ejects device and reboots Kobo
|
||||||
9. User ejects device and reboots Kobo
|
|
||||||
|
### Manual mode (all browsers)
|
||||||
|
1. Select firmware version from dropdown
|
||||||
|
2. Select Kobo model from dropdown (determines firmware download URL)
|
||||||
|
3. Configure patches, click "Build"
|
||||||
|
4. Download `KoboRoot.tgz` and manually copy to `.kobo/` on device
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
### `kobo-device.js` — Device Access
|
### `kobo-device.js` — Device Access & Firmware URLs
|
||||||
- File System Access API for reading `.kobo/version`
|
- File System Access API for reading `.kobo/version`
|
||||||
- Serial prefix → model name mapping
|
- Serial prefix → model name mapping (`KOBO_MODELS`)
|
||||||
|
- Firmware download URLs per version and device prefix (`FIRMWARE_DOWNLOADS`)
|
||||||
- Writing `KoboRoot.tgz` back to `.kobo/`
|
- Writing `KoboRoot.tgz` back to `.kobo/`
|
||||||
|
|
||||||
### `patch-ui.js` — Patch Configuration
|
### `patch-ui.js` — Patch Configuration
|
||||||
- Parses patch YAML files (bundled or fetched)
|
- Parses patch YAML files from zip archives (handles CRLF line endings)
|
||||||
- Renders patch list with toggles grouped by target file
|
- Renders patch list with toggles grouped by target file
|
||||||
- Enforces PatchGroup mutual exclusion
|
- Enforces PatchGroup mutual exclusion (radio buttons)
|
||||||
- Generates overrides config
|
- Generates kobopatch.yaml config with overrides from UI state
|
||||||
|
|
||||||
|
### `patch-worker.js` — Web Worker (in progress)
|
||||||
|
- Loads `wasm_exec.js` and `kobopatch.wasm` off the main thread
|
||||||
|
- Receives patch config + firmware via `postMessage`
|
||||||
|
- Sends progress updates back to main thread for live UI rendering
|
||||||
|
- Returns `KoboRoot.tgz` bytes via transferable buffer
|
||||||
|
|
||||||
### `kobopatch.wasm` — Patching Engine
|
### `kobopatch.wasm` — Patching Engine
|
||||||
- Go source in `kobopatch-src/`, compiled with `GOOS=js GOARCH=wasm`
|
- Go source in `kobopatch-wasm/`, compiled with `GOOS=js GOARCH=wasm`
|
||||||
- Custom WASM wrapper accepts in-memory inputs:
|
- Custom WASM wrapper accepts in-memory inputs:
|
||||||
- Config YAML (generated from UI state)
|
- Config YAML (generated from UI state)
|
||||||
- Firmware zip (from user file picker)
|
- Firmware zip (auto-downloaded from Kobo CDN)
|
||||||
- Patch YAML files (bundled)
|
- Patch YAML files (from bundled zip)
|
||||||
- Returns `KoboRoot.tgz` bytes
|
- Optional progress callback (4th argument) for real-time status
|
||||||
|
- Returns `{ tgz: Uint8Array, log: string }`
|
||||||
- No filesystem or exec calls — everything in-memory
|
- No filesystem or exec calls — everything in-memory
|
||||||
|
|
||||||
|
### `kobopatch.js` — Runner Interface
|
||||||
|
- Abstracts WASM loading and invocation
|
||||||
|
- Will be updated to communicate with Web Worker
|
||||||
|
|
||||||
### Static Assets
|
### Static Assets
|
||||||
- Patch YAML files from `kobopatch/src/*.yaml`
|
- Patch config zips in `src/public/patches/` with `index.json` index
|
||||||
- `wasm_exec.js` (Go's WASM support JS)
|
- `wasm_exec.js` (Go's WASM support JS)
|
||||||
- The WASM binary itself
|
- The WASM binary itself
|
||||||
|
|
||||||
@@ -56,29 +76,41 @@ Browser
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
frontend/
|
public/ # Webroot (static hosting)
|
||||||
index.html # Single page app
|
index.html # Single page app
|
||||||
style.css # Styling
|
style.css # Styling
|
||||||
app.js # Main controller / flow orchestration
|
app.js # Main controller / flow orchestration
|
||||||
kobo-device.js # File System Access API + device identification
|
kobo-device.js # File System Access API + device identification + firmware URLs
|
||||||
patch-ui.js # Patch list rendering + toggle logic (TODO)
|
patch-ui.js # Patch list rendering + toggle logic
|
||||||
wasm_exec.js # Go WASM support (from Go SDK)
|
kobopatch.js # WASM runner interface
|
||||||
kobopatch.wasm # Compiled WASM binary
|
patch-worker.js # Web Worker for off-thread patching
|
||||||
patches/ # Bundled patch YAML files
|
wasm_exec.js # Go WASM support (from Go SDK, gitignored)
|
||||||
kobopatch-src/ # Cloned kobopatch Go source
|
kobopatch.wasm # Compiled WASM binary (gitignored)
|
||||||
wasm/ # WASM wrapper (to be created)
|
patches/
|
||||||
|
index.json # Available patch versions
|
||||||
|
patches_*.zip # Patch config zips (kobopatch.yaml + src/*.yaml)
|
||||||
|
kobopatch-wasm/
|
||||||
|
main.go # WASM entry point + patching pipeline
|
||||||
|
go.mod # Go module (replaces for kobopatch + yaml fork)
|
||||||
|
setup.sh # Clones kobopatch source, copies wasm_exec.js
|
||||||
|
build.sh # Compiles WASM, copies artifacts to src/public/
|
||||||
|
kobopatch-src/ # Cloned kobopatch Go source (gitignored)
|
||||||
|
wip/ # Planning docs
|
||||||
|
.github/workflows/ # CI (GitHub Actions / Gitea compatible)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Constraints
|
## Key Constraints
|
||||||
|
|
||||||
- **Chromium-only**: File System Access API not available in Firefox/Safari
|
- **Chromium-only for auto mode**: File System Access API not in Firefox/Safari
|
||||||
- Fallback: offer KoboRoot.tgz as download with manual copy instructions
|
- Manual mode fallback: select model + firmware version from dropdowns
|
||||||
- **User provides firmware**: we don't host firmware files (legal reasons)
|
- **Firmware auto-download**: fetched from `ereaderfiles.kobo.com` (CORS allows `*`)
|
||||||
- **WASM binary size**: Go WASM builds are typically 5-15MB
|
- URLs hardcoded per device prefix in `FIRMWARE_DOWNLOADS`
|
||||||
- Mitigated by gzip compression on static hosting (~2-5MB)
|
- Download URL displayed to user for verification
|
||||||
- **Memory usage**: firmware zips are ~150MB, patching happens in-memory
|
- **WASM binary size**: ~9.9MB uncompressed
|
||||||
|
- Mitigated by gzip compression on static hosting (~3-4MB)
|
||||||
|
- **Memory usage**: firmware zips are ~150-300MB, patching happens in-memory
|
||||||
- Should be fine on modern desktops (USB implies desktop use)
|
- Should be fine on modern desktops (USB implies desktop use)
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
Any static file server, e.g.: `python3 -m http.server -d src/frontend/ 8080`
|
Any static file server, e.g.: `python3 -m http.server -d src/public/ 8888`
|
||||||
|
|||||||
29
wip/todo.md
29
wip/todo.md
@@ -17,20 +17,29 @@
|
|||||||
- [x] JSZip for client-side zip extraction
|
- [x] JSZip for client-side zip extraction
|
||||||
- [x] Renamed `src/frontend` → `src/public` (webroot)
|
- [x] Renamed `src/frontend` → `src/public` (webroot)
|
||||||
- [x] Moved `patches/` into `src/public/patches/`
|
- [x] Moved `patches/` into `src/public/patches/`
|
||||||
|
- [x] Manual mode fallback for non-Chromium browsers (model + firmware version dropdowns)
|
||||||
|
- [x] Auto-download firmware from Kobo's servers (CORS confirmed working)
|
||||||
|
- [x] Firmware download URLs hardcoded per device prefix and version
|
||||||
|
- [x] Firmware download URL displayed in build step for user verification
|
||||||
|
- [x] Fixed CRLF line ending bug in patch YAML parser
|
||||||
|
- [x] Copy `kobopatch.wasm` + `wasm_exec.js` to `src/public/` as part of build
|
||||||
|
- [x] Progress reporting: download % with MB, WASM log output in terminal window
|
||||||
|
- [x] WASM `patchFirmware` accepts optional progress callback (4th arg)
|
||||||
|
- [x] Verified patched binaries are byte-identical between native and WASM builds
|
||||||
|
|
||||||
## To Test
|
## To Test
|
||||||
|
|
||||||
- [ ] End-to-end test in browser with real Kobo device + firmware zip
|
- [ ] End-to-end test in browser with real Kobo device (auto mode)
|
||||||
- [ ] Verify WASM loads and `patchFirmware()` works in browser (not just Node.js)
|
|
||||||
- [ ] Verify patch YAML parser handles all 6 patch files correctly
|
|
||||||
- [ ] Verify File System Access API write to `.kobo/KoboRoot.tgz`
|
- [ ] Verify File System Access API write to `.kobo/KoboRoot.tgz`
|
||||||
- [ ] Verify download fallback works
|
- [ ] Test manual mode flow across Firefox/Safari/Chrome
|
||||||
|
|
||||||
|
## In Progress
|
||||||
|
|
||||||
|
- [ ] Web Worker for WASM patching (avoid blocking UI, enable live progress)
|
||||||
|
- `patch-worker.js` created, not yet wired up
|
||||||
|
|
||||||
## Remaining Work
|
## Remaining Work
|
||||||
|
|
||||||
- [ ] Copy `kobopatch.wasm` + `wasm_exec.js` to `src/public/` as part of build
|
|
||||||
- [ ] Run WASM patching in a Web Worker (avoid blocking UI during build)
|
|
||||||
- [ ] Loading/progress feedback during WASM load + build
|
|
||||||
- [ ] Better error messages for common failures
|
- [ ] Better error messages for common failures
|
||||||
- [ ] Test with multiple firmware versions / patch zips
|
- [ ] Test with multiple firmware versions / patch zips
|
||||||
|
|
||||||
@@ -46,4 +55,8 @@
|
|||||||
Reason: avoid storing Kobo firmware files on a server (legal risk).
|
Reason: avoid storing Kobo firmware files on a server (legal risk).
|
||||||
- **Patches served from zip files in `src/public/patches/`.**
|
- **Patches served from zip files in `src/public/patches/`.**
|
||||||
App scans `patches/index.json` to find compatible patch zips for the detected firmware.
|
App scans `patches/index.json` to find compatible patch zips for the detected firmware.
|
||||||
User provides their own firmware zip. kobopatch runs as WASM in the browser.
|
- **Firmware auto-downloaded from Kobo's CDN.**
|
||||||
|
`ereaderfiles.kobo.com` serves `Access-Control-Allow-Origin: *`, so direct `fetch()` works.
|
||||||
|
User no longer needs to provide firmware manually. URLs hardcoded in `kobo-device.js`.
|
||||||
|
- **Web Worker for WASM (in progress).**
|
||||||
|
Moves patching off the main thread so progress updates render live during the build.
|
||||||
|
|||||||
Reference in New Issue
Block a user