Troubleshooting
-          Cannot enter fastboot or device says "Press any key to continue"
+          Too slow
+          
+            It is recommended that you use a USB 3.0 cable when flashing since it will speed up the flashing time by a lot.
+          
+          Cannot enter QDL
           
             Try using a different USB cable or USB port. Sometimes USB 2.0 ports work better than USB 3.0 (blue) ports.
             If you're using a USB hub, try connecting the device directly to your computer, or alternatively use a
@@ -121,9 +120,8 @@ export default function App() {
           
           My device's screen is blank
           
-            The device can still be in fastboot mode and reflashed normally if the screen isn't displaying
-            anything. A blank screen is usually caused by installing older software that doesn't support newer
-            displays. If a reflash doesn't fix the blank screen, then the device's display may be damaged.
+            The device screen will be blank in QDL mode, but you can verify that it is in QDL if the device shows up
+            when you press the Flash icon.
           
           After flashing, device says unable to mount data partition
           
diff --git a/src/assets/zadig_create_new_device.png b/src/assets/zadig_create_new_device.png
index 4e48555d..1b252f6e 100644
Binary files a/src/assets/zadig_create_new_device.png and b/src/assets/zadig_create_new_device.png differ
diff --git a/src/assets/zadig_form.png b/src/assets/zadig_form.png
index 346bb12a..2bc01ee6 100644
Binary files a/src/assets/zadig_form.png and b/src/assets/zadig_form.png differ
diff --git a/src/config.js b/src/config.js
index c5169486..4fb834b7 100644
--- a/src/config.js
+++ b/src/config.js
@@ -3,6 +3,9 @@ const config = {
     release: 'https://raw.githubusercontent.com/commaai/openpilot/release3/system/hardware/tici/agnos.json',
     master: 'https://raw.githubusercontent.com/commaai/openpilot/master/system/hardware/tici/agnos.json',
   },
+  loader: {
+    url: 'https://raw.githubusercontent.com/commaai/flash/master/src/QDL/sdm845_fhprg.bin',
+  },
 }
 
 export default config
diff --git a/src/utils/fastboot.js b/src/utils/flash.js
similarity index 65%
rename from src/utils/fastboot.js
rename to src/utils/flash.js
index 4e97eddf..071df500 100644
--- a/src/utils/fastboot.js
+++ b/src/utils/flash.js
@@ -1,6 +1,6 @@
 import { useEffect, useRef, useState } from 'react'
-
-import { FastbootDevice, setDebugLevel } from 'android-fastboot'
+import { qdlDevice } from '@commaai/qdl'
+import { concatUint8Array } from '@commaai/qdl/utils'
 import * as Comlink from 'comlink'
 
 import config from '../config'
@@ -13,9 +13,6 @@ import { withProgress } from '../utils/progress'
  * @typedef {import('./manifest.js').Image} Image
  */
 
-// Verbose logging for fastboot
-setDebugLevel(2)
-
 export const Step = {
   INITIALIZING: 0,
   READY: 1,
@@ -40,27 +37,11 @@ export const Error = {
   REQUIREMENTS_NOT_MET: 8,
 }
 
-function isRecognizedDevice(deviceInfo) {
-  // check some variables are as expected for a comma three
-  const {
-    kernel,
-    "max-download-size": maxDownloadSize,
-    "slot-count": slotCount,
-  } = deviceInfo
-  if (kernel !== "uefi" || maxDownloadSize !== "104857600" || slotCount !== "2") {
-    console.error('[fastboot] Unrecognised device (kernel, maxDownloadSize or slotCount)', deviceInfo)
-    return false
-  }
+function isRecognizedDevice(slotCount, partitions) {
 
-  const partitions = []
-  for (const key of Object.keys(deviceInfo)) {
-    if (!key.startsWith("partition-type:")) continue
-    let partition = key.substring("partition-type:".length)
-    if (partition.endsWith("_a") || partition.endsWith("_b")) {
-      partition = partition.substring(0, partition.length - 2)
-    }
-    if (partitions.includes(partition)) continue
-    partitions.push(partition)
+  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
@@ -73,20 +54,14 @@ function isRecognizedDevice(deviceInfo) {
     "xbl_config"
   ]
   if (!partitions.every(partition => expectedPartitions.includes(partition))) {
-    console.error('[fastboot] Unrecognised device (partitions)', partitions)
+    console.error('[QDL] Unrecognised device (partitions)', partitions)
     return false
   }
-
-  // sanity check, also useful for logging
-  if (!deviceInfo['serialno']) {
-    console.error('[fastboot] Unrecognised device (missing serialno)', deviceInfo)
-    return false
-  }
-
   return true
 }
 
-export function useFastboot() {
+
+export function useQdl() {
   const [step, _setStep] = useState(Step.INITIALIZING)
   const [message, _setMessage] = useState('')
   const [progress, setProgress] = useState(0)
@@ -99,7 +74,7 @@ export function useFastboot() {
   const [onRetry, setOnRetry] = useState(null)
 
   const imageWorker = useImageWorker()
-  const fastboot = useRef(new FastbootDevice())
+  const qdl = useRef(new qdlDevice())
 
   /** @type {React.RefObject} */
   const manifest = useRef(null)
@@ -109,21 +84,20 @@ export function useFastboot() {
   }
 
   function setMessage(message = '') {
-    if (message) console.info('[fastboot]', message)
+    if (message) console.info('[QDL]', message)
     _setMessage(message)
   }
 
   function setError(error) {
     _setError(error)
   }
-
   useEffect(() => {
     setProgress(-1)
     setMessage()
 
     if (error) return
     if (!imageWorker.current) {
-      console.debug('[fastboot] Waiting for image worker')
+      console.debug('[QDL] Waiting for image worker')
       return
     }
 
@@ -131,21 +105,21 @@ export function useFastboot() {
       case Step.INITIALIZING: {
         // Check that the browser supports WebUSB
         if (typeof navigator.usb === 'undefined') {
-          console.error('[fastboot] WebUSB not supported')
+          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('[fastboot] Web Workers not supported')
+          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('[fastboot] Storage API not supported')
+          console.error('[QDL] Storage API not supported')
           setError(Error.REQUIREMENTS_NOT_MET)
           break
         }
@@ -161,11 +135,11 @@ export function useFastboot() {
               throw 'Manifest is empty'
             }
 
-            console.debug('[fastboot] Loaded manifest', manifest.current)
+            console.debug('[QDL] Loaded manifest', manifest.current)
             setStep(Step.READY)
           })
           .catch((err) => {
-            console.error('[fastboot] Initialization error', err)
+            console.error('[QDL] Initialization error', err)
             setError(Error.UNKNOWN)
           })
         break
@@ -181,46 +155,38 @@ export function useFastboot() {
       }
 
       case Step.CONNECTING: {
-        fastboot.current.waitForConnect()
+        qdl.current.waitForConnect()
           .then(() => {
-            console.info('[fastboot] Connected', { fastboot: fastboot.current })
-            return fastboot.current.getVariable('all')
-              .then((all) => {
-                const deviceInfo = all.split('\n').reduce((obj, line) => {
-                  const parts = line.split(':')
-                  const key = parts.slice(0, -1).join(':').trim()
-                  obj[key] = parts.slice(-1)[0].trim()
-                  return obj
-                }, {})
-
-                const recognized = isRecognizedDevice(deviceInfo)
-                console.debug('[fastboot] Device info', { recognized, deviceInfo })
+            console.info('[QDL] Connected')
+            return qdl.current.getDevicePartitionsInfo()
+              .then(([slotCount, partitions]) => {
+                const recognized = isRecognizedDevice(slotCount, partitions)
+                console.debug('[QDL] Device info', { recognized,  partitions})
 
                 if (!recognized) {
                   setError(Error.UNRECOGNIZED_DEVICE)
                   return
                 }
 
-                setSerial(deviceInfo['serialno'] || 'unknown')
+                setSerial(qdl.current.sahara.serial || 'unknown')
                 setConnected(true)
                 setStep(Step.DOWNLOADING)
               })
               .catch((err) => {
-                console.error('[fastboot] Error getting device information', err)
+                console.error('[QDL] Error getting device information', err)
                 setError(Error.UNKNOWN)
               })
           })
           .catch((err) => {
-            console.error('[fastboot] Connection lost', err)
+            console.error('[QDL] Connection lost', err)
             setError(Error.LOST_CONNECTION)
             setConnected(false)
           })
-
-        fastboot.current.connect()
+        qdl.current.connect()
           .catch((err) => {
-            console.error('[fastboot] Connection error', err)
+            console.error('[QDL] Connection error', err)
             setStep(Step.READY)
-          })
+        })
         break
       }
 
@@ -236,11 +202,11 @@ export function useFastboot() {
 
         downloadImages()
           .then(() => {
-            console.debug('[fastboot] Downloaded all images')
+            console.debug('[QDL] Downloaded all images')
             setStep(Step.UNPACKING)
           })
           .catch((err) => {
-            console.error('[fastboot] Download error', err)
+            console.error('[QDL] Download error', err)
             setError(Error.DOWNLOAD_FAILED)
           })
         break
@@ -258,11 +224,11 @@ export function useFastboot() {
 
         unpackImages()
           .then(() => {
-            console.debug('[fastboot] Unpacked all images')
+            console.debug('[QDL] Unpacked all images')
             setStep(Step.FLASHING)
           })
           .catch((err) => {
-            console.error('[fastboot] Unpack error', err)
+            console.error('[QDL] Unpack error', err)
             if (err.startsWith('Checksum mismatch')) {
               setError(Error.CHECKSUM_MISMATCH)
             } else {
@@ -276,36 +242,37 @@ export function useFastboot() {
         setProgress(0)
 
         async function flashDevice() {
-          const currentSlot = await fastboot.current.getVariable('current-slot')
+          const currentSlot = await qdl.current.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}`)
 
           for await (const [image, onProgress] of withProgress(manifest.current, setProgress)) {
             const fileHandle = await imageWorker.current.getImage(image)
             const blob = await fileHandle.getFile()
 
-            if (image.sparse) {
-              setMessage(`Erasing ${image.name}`)
-              await fastboot.current.runCommand(`erase:${image.name}`)
-            }
             setMessage(`Flashing ${image.name}`)
-            await fastboot.current.flashBlob(image.name, blob, onProgress, 'other')
+            const partitionName = image.name + `_${otherSlot}`
+            await qdl.current.flashBlob(partitionName, blob, onProgress)
           }
-          console.debug('[fastboot] Flashed all partitions')
+          console.debug('[QDL] Flashed all partitions')
 
-          const otherSlot = currentSlot === 'a' ? 'b' : 'a'
           setMessage(`Changing slot to ${otherSlot}`)
-          await fastboot.current.runCommand(`set_active:${otherSlot}`)
+          await qdl.current.setActiveSlot(otherSlot)
         }
 
         flashDevice()
           .then(() => {
-            console.debug('[fastboot] Flash complete')
+            console.debug('[QDL] Flash complete')
             setStep(Step.ERASING)
           })
           .catch((err) => {
-            console.error('[fastboot] Flashing error', err)
+            console.error('[QDL] Flashing error', err)
             setError(Error.FLASH_FAILED)
           })
         break
@@ -314,24 +281,30 @@ export function useFastboot() {
       case Step.ERASING: {
         setProgress(0)
 
+        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)
+        }
+
         async function eraseDevice() {
           setMessage('Erasing userdata')
-          await fastboot.current.runCommand('erase:userdata')
+          await resetUserdata()
           setProgress(0.9)
 
           setMessage('Rebooting')
-          await fastboot.current.runCommand('continue')
+          await qdl.current.reset()
           setProgress(1)
           setConnected(false)
         }
 
         eraseDevice()
           .then(() => {
-            console.debug('[fastboot] Erase complete')
+            console.debug('[QDL] Erase complete')
             setStep(Step.DONE)
           })
           .catch((err) => {
-            console.error('[fastboot] Erase error', err)
+            console.error('[QDL] Erase error', err)
             setError(Error.ERASE_FAILED)
           })
         break
@@ -341,12 +314,12 @@ export function useFastboot() {
 
   useEffect(() => {
     if (error !== Error.NONE) {
-      console.debug('[fastboot] error', error)
+      console.debug('[QDL] error', error)
       setProgress(-1)
       setOnContinue(null)
 
       setOnRetry(() => () => {
-        console.debug('[fastboot] on retry')
+        console.debug('[QDL] on retry')
         window.location.reload()
       })
     }
@@ -365,3 +338,4 @@ export function useFastboot() {
     onRetry,
   }
 }
+