-
+
+
+
+
{detachScript.map((line, index) => (
-
+
{line}
))}
-
+
@@ -289,15 +291,15 @@ export default function Flash() {
>
)}
- {error && (
+ {error() && (
- ) || false}
- {connected &&
}
+ )}
+ {connected() &&
}
)
-}
+}
\ No newline at end of file
diff --git a/src/app/index.jsx b/src/app/index.jsx
index cd4369f..235879c 100644
--- a/src/app/index.jsx
+++ b/src/app/index.jsx
@@ -1,4 +1,4 @@
-import { Suspense, lazy } from 'react'
+import { createResource, lazy, Suspense } from 'solid-js'
import comma from '../assets/comma.svg'
import fastbootPorts from '../assets/fastboot-ports.svg'
@@ -9,12 +9,13 @@ const Flash = lazy(() => import('./Flash'))
export default function App() {
const version = import.meta.env.VITE_PUBLIC_GIT_SHA || 'dev'
- console.info(`flash.comma.ai version: ${version}`);
+ console.info(`flash.comma.ai version: ${version}`)
+
return (
-
-
+
+
-
+
flash.comma.ai
This tool allows you to flash AGNOS onto your comma device.
@@ -140,19 +141,19 @@ export default function App() {
-
+
flash.comma.ai version: {version}
-
-
Loading...}>
+
+ Loading...}>
-
+
flash.comma.ai version: {version.substring(0, 7)}
diff --git a/src/main.jsx b/src/main.jsx
index 40fea3e..07c6732 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,5 +1,4 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
+import { render } from 'solid-js/web'
import '@fontsource-variable/inter'
import '@fontsource-variable/jetbrains-mono'
@@ -7,8 +6,12 @@ import '@fontsource-variable/jetbrains-mono'
import './index.css'
import App from './app'
-ReactDOM.createRoot(document.getElementById('root')).render(
-
-
- ,
-)
+const root = document.getElementById('root')
+
+if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
+ throw new Error(
+ 'Root element not found',
+ )
+}
+
+render(() => , root)
diff --git a/src/utils/flash.js b/src/utils/flash.js
index 0fe2776..2edee36 100644
--- a/src/utils/flash.js
+++ b/src/utils/flash.js
@@ -1,4 +1,4 @@
-import { useEffect, useRef, useState } from 'react'
+import { createSignal, createEffect, onCleanup } from 'solid-js'
import { concatUint8Array } from '@/QDL/utils'
import { qdlDevice } from '@/QDL/qdl'
@@ -10,10 +10,6 @@ import { useImageWorker } from '@/utils/image'
import { createManifest } from '@/utils/manifest'
import { withProgress } from '@/utils/progress'
-/**
- * @typedef {import('./manifest.js').Image} Image
- */
-
export const Step = {
INITIALIZING: 0,
READY: 1,
@@ -39,13 +35,11 @@ export const Error = {
}
function isRecognizedDevice(slotCount, partitions) {
-
if (slotCount !== 2) {
console.error('[QDL] Unrecognised device (slotCount)')
return false
}
- // check we have the expected partitions to make sure it's a comma three
const expectedPartitions = [
"ALIGN_TO_128K_1", "ALIGN_TO_128K_2", "ImageFv", "abl", "aop", "apdp", "bluetooth", "boot", "cache",
"cdt", "cmnlib", "cmnlib64", "ddr", "devcfg", "devinfo", "dip", "dsp", "fdemeta", "frp", "fsc", "fsg",
@@ -61,82 +55,77 @@ function isRecognizedDevice(slotCount, partitions) {
return true
}
-
export function useQdl() {
- const [step, _setStep] = useState(Step.INITIALIZING)
- const [message, _setMessage] = useState('')
- const [progress, setProgress] = useState(0)
- const [error, _setError] = useState(Error.NONE)
+ const [step, _setStep] = createSignal(Step.INITIALIZING)
+ const [message, _setMessage] = createSignal('')
+ const [progress, setProgress] = createSignal(0)
+ const [error, _setError] = createSignal(Error.NONE)
- const [connected, setConnected] = useState(false)
- const [serial, setSerial] = useState(null)
+ const [connected, setConnected] = createSignal(false)
+ const [serial, setSerial] = createSignal(null)
- const [onContinue, setOnContinue] = useState(null)
- const [onRetry, setOnRetry] = useState(null)
+ const [onContinue, setOnContinue] = createSignal(null)
+ const [onRetry, setOnRetry] = createSignal(null)
const imageWorker = useImageWorker()
- const qdl = useRef(new qdlDevice())
+ const [qdl] = createSignal(new qdlDevice())
- /** @type {React.RefObject} */
- const manifest = useRef(null)
+ const [manifest, setManifest] = createSignal(null)
- function setStep(step) {
- _setStep(step)
+ function setStep(newStep) {
+ _setStep(newStep)
}
- function setMessage(message = '') {
- if (message) console.info('[QDL]', message)
- _setMessage(message)
+ function setMessage(newMessage = '') {
+ if (newMessage) console.info('[QDL]', newMessage)
+ _setMessage(newMessage)
}
- function setError(error) {
- _setError(error)
+ function setError(newError) {
+ _setError(newError)
}
- useEffect(() => {
+
+ createEffect(() => {
setProgress(-1)
setMessage()
- if (error) return
- if (!imageWorker.current) {
+ if (error()) return
+ if (!imageWorker().current) {
console.debug('[QDL] Waiting for image worker')
return
}
- switch (step) {
+ switch (step()) {
case Step.INITIALIZING: {
- // Check that the browser supports WebUSB
if (typeof navigator.usb === 'undefined') {
console.error('[QDL] WebUSB not supported')
setError(Error.REQUIREMENTS_NOT_MET)
break
}
- // Check that the browser supports Web Workers
if (typeof Worker === 'undefined') {
console.error('[QDL] Web Workers not supported')
setError(Error.REQUIREMENTS_NOT_MET)
break
}
- // Check that the browser supports Storage API
if (typeof Storage === 'undefined') {
console.error('[QDL] Storage API not supported')
setError(Error.REQUIREMENTS_NOT_MET)
break
}
- imageWorker.current?.init()
+ imageWorker().current?.init()
.then(() => download(config.manifests['release']))
.then(blob => blob.text())
.then(text => {
- manifest.current = createManifest(text)
+ setManifest(createManifest(text))
- // sanity check
- if (manifest.current.length === 0) {
+ if (manifest().length === 0) {
throw 'Manifest is empty'
}
- console.debug('[QDL] Loaded manifest', manifest.current)
+ console.debug('[QDL] Loaded manifest', manifest())
setStep(Step.READY)
})
.catch((err) => {
@@ -147,7 +136,6 @@ export function useQdl() {
}
case Step.READY: {
- // wait for user interaction (we can't use WebUSB without user event)
setOnContinue(() => () => {
setOnContinue(null)
setStep(Step.CONNECTING)
@@ -156,10 +144,10 @@ export function useQdl() {
}
case Step.CONNECTING: {
- qdl.current.waitForConnect()
+ qdl().waitForConnect()
.then(() => {
console.info('[QDL] Connected')
- return qdl.current.getDevicePartitionsInfo()
+ return qdl().getDevicePartitionsInfo()
.then(([slotCount, partitions]) => {
const recognized = isRecognizedDevice(slotCount, partitions)
console.debug('[QDL] Device info', { recognized, partitions})
@@ -169,7 +157,7 @@ export function useQdl() {
return
}
- setSerial(qdl.current.sahara.serial || 'unknown')
+ setSerial(qdl().sahara.serial || 'unknown')
setConnected(true)
setStep(Step.DOWNLOADING)
})
@@ -183,7 +171,7 @@ export function useQdl() {
setError(Error.LOST_CONNECTION)
setConnected(false)
})
- qdl.current.connect()
+ qdl().connect()
.catch((err) => {
console.error('[QDL] Connection error', err)
setStep(Step.READY)
@@ -195,9 +183,9 @@ export function useQdl() {
setProgress(0)
async function downloadImages() {
- for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) {
+ for await (const [image, onProgress] of withProgress(manifest(), setProgress)) {
setMessage(`Downloading ${image.name}`)
- await imageWorker.current.downloadImage(image, Comlink.proxy(onProgress))
+ await imageWorker().current.downloadImage(image, Comlink.proxy(onProgress))
}
}
@@ -217,9 +205,9 @@ export function useQdl() {
setProgress(0)
async function unpackImages() {
- for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) {
+ for await (const [image, onProgress] of withProgress(manifest(), setProgress)) {
setMessage(`Unpacking ${image.name}`)
- await imageWorker.current.unpackImage(image, Comlink.proxy(onProgress))
+ await imageWorker().current.unpackImage(image, Comlink.proxy(onProgress))
}
}
@@ -243,28 +231,26 @@ export function useQdl() {
setProgress(0)
async function flashDevice() {
- const currentSlot = await qdl.current.getActiveSlot();
+ const currentSlot = await qdl().getActiveSlot();
if (!['a', 'b'].includes(currentSlot)) {
throw `Unknown current slot ${currentSlot}`
}
const otherSlot = currentSlot === 'a' ? 'b' : 'a'
- // Erase current xbl partition so if users try to power up device
- // with corrupted primary gpt header, it would not update the backup
- await qdl.current.erase("xbl"+`_${currentSlot}`)
+ await qdl().erase("xbl"+`_${currentSlot}`)
- for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) {
- const fileHandle = await imageWorker.current.getImage(image)
+ for await (const [image, onProgress] of withProgress(manifest(), setProgress)) {
+ const fileHandle = await imageWorker().current.getImage(image)
const blob = await fileHandle.getFile()
setMessage(`Flashing ${image.name}`)
const partitionName = image.name + `_${otherSlot}`
- await qdl.current.flashBlob(partitionName, blob, onProgress)
+ await qdl().flashBlob(partitionName, blob, onProgress)
}
console.debug('[QDL] Flashed all partitions')
setMessage(`Changing slot to ${otherSlot}`)
- await qdl.current.setActiveSlot(otherSlot)
+ await qdl().setActiveSlot(otherSlot)
}
flashDevice()
@@ -284,8 +270,8 @@ export function useQdl() {
async function resetUserdata() {
let wData = new TextEncoder().encode("COMMA_RESET")
- wData = new Blob([concatUint8Array([wData, new Uint8Array(28 - wData.length).fill(0)])]) // make equal sparseHeaderSize
- await qdl.current.flashBlob("userdata", wData)
+ wData = new Blob([concatUint8Array([wData, new Uint8Array(28 - wData.length).fill(0)])])
+ await qdl().flashBlob("userdata", wData)
}
async function eraseDevice() {
@@ -294,7 +280,7 @@ export function useQdl() {
setProgress(0.9)
setMessage('Rebooting')
- await qdl.current.reset()
+ await qdl().reset()
setProgress(1)
setConnected(false)
}
@@ -311,11 +297,11 @@ export function useQdl() {
break
}
}
- }, [error, imageWorker, step])
+ })
- useEffect(() => {
- if (error !== Error.NONE) {
- console.debug('[QDL] error', error)
+ createEffect(() => {
+ if (error() !== Error.NONE) {
+ console.debug('[QDL] error', error())
setProgress(-1)
setOnContinue(null)
@@ -324,19 +310,16 @@ export function useQdl() {
window.location.reload()
})
}
- }, [error])
+ })
return {
step,
message,
progress,
error,
-
connected,
serial,
-
onContinue,
onRetry,
}
-}
-
+}
\ No newline at end of file
diff --git a/src/utils/image.js b/src/utils/image.js
index ed27f09..f781b66 100644
--- a/src/utils/image.js
+++ b/src/utils/image.js
@@ -1,17 +1,21 @@
-import { useEffect, useRef } from 'react'
-
+import { createSignal, createEffect, onCleanup } from 'solid-js'
import * as Comlink from 'comlink'
export function useImageWorker() {
- const apiRef = useRef()
+ const [apiRef, setApiRef] = createSignal({ current: null })
- useEffect(() => {
+ createEffect(() => {
const worker = new Worker(new URL('../workers/image.worker', import.meta.url), {
type: 'module',
})
- apiRef.current = Comlink.wrap(worker)
- return () => worker.terminate()
- }, [])
+ const wrappedWorker = Comlink.wrap(worker)
+ setApiRef({ current: wrappedWorker })
+
+ onCleanup(() => {
+ worker.terminate()
+ setApiRef({ current: null })
+ })
+ })
return apiRef
}
diff --git a/vite.config.js b/vite.config.js
index ba64cd9..1542842 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,10 +1,10 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import solidPlugin from 'vite-plugin-solid'
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [solidPlugin()],
resolve: {
alias: [
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },