Being a curious sort of guy – and having an inexplicable love for tinkering with large format printers – I couldn’t pass up an offer for a really cheap, but non functioning, DesignJet T520. Can I fix it?
It had the dreaded BSOD (Blue Screen of Death) which seems to have plagued many, many users on the internet. At least according to Google.

The remedy seems to be either to:
- Call HP Service and pay an absurd amount of money for a fix
- Order an “HP Encryption USB Key” (part number CQ890-67105)
- Invent some sort of way of fixing it yourself
The guy I bought it from was told by HP that they estimated the repair at around $900, but it could run further up if it was a “serious problem”. Let’s hope it’s not, so I’m not paying that much to get this fixed. So the first option is ruled out.
Second option almost seems like cheating, doesn’t it? I mean, inventive people on ebay are selling these USB keys, and they don’t look original to me. Option two is clearly an option though, as an original key is quite cheap at around $40 here in Denmark.
As the last one is by far the most time wasting and challenging, I just *had* to try it. Who knows – I might fail – and I might succeed. Either way, I’ll probably learn something along the way.
So step 1 was to dismantle the printer, to get to the logic board. This requires you to remove the lover front panel, by yanking it out (it’s held in with 6 clips), and removing 5-8 screws from the left panel. Then slide that off, and you have access to the main board. There are three screws holding that in place, so remove those too.

To remove the “HP Encryption USB Key” you probably need to remove a small screw holding it in place (accessible from the front of the main board). Then remove whatever cables needed from the upper portion of the main board (be careful with the wide LCD cable). Then carefully remove the USB key – I was able to do it without removing the board entirely.

So with the mysterious key in hand, I plugged it into a Linux machine, where it appeared as a 1.9GB FAT32 formatted drive. WTF – everywhere I look, it’s reported as a non-standard USB key. This seems to be an outright lie, getting you to choose option 1 or perhaps 2 instead.

Disk /dev/sdc: 1.9 GiB, 2003828736 bytes, 3913728 sectors
Disk model: USB DISK 2.0
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
Device Boot Start End Sectors Size Id Type
/dev/sdc1 63 65598 65536 32M b W95 FAT32
/dev/sdc2 65599 131134 65536 32M b W95 FAT32
/dev/sdc3 131135 3911679 3780545 1.8G b W95 FAT32
Further investigation (fdisk) showed this MBR partition table:
So three partitions, of which only the last one was formatted as a drive. I dd’ed all data onto my computer and started looking at it. The MBR is just an MBR partition table with no boot code at all.
Partition 1 is firmware and so is partition 2. If I was to make an updatable printer, I’d put two copies of the same firmware in the system, and have the printer boot off the first one. If that fails (firmware update aborted or other failure), I’d fall back to the secondary. This is obviously what the HP guys thought too, so there was these firmwares on each partition:
Partition 1: AXP2CN1437AR....Tue Sep 09, 2014 08:32:54AM
Partition 2: AXP2CN1325AR....Tue Jun 18, 2013 12:01:45AM
OK, so the USB drive is just an USB drive – I’ll just get another one (4GB Kingston) and dd everything back onto this one. Perhaps that would work?

…… no dice. Printer just stands there after loading a while, total black screen and beeps in disgust: “Try harder, human”.
Back at the drawing board, I tried dd’ing the secondary partition onto the primary partition, using the original HP drive. Plugged that into the printer – same result. OK, that’s really strange I thought – perhaps I didn’t dd it correctly. So I copied partition1 back onto the computer and compared it to the dump of partition2
Binary files hp-t520-fw-partition1 and x1 differ
WHAT? Whatwhatwhat … aaaah, okay, so the USB key is actually broken. I mean it works, but whatever you put on the key is not what you get back. So the onboard NAND is hosed. This explains why neither the primary firmware or secondary works.
Alright – off to HPs site, download a new firmware file, and put that on the Kingston drive? But no – not so easy. Firmware files for this printer is in the .FUL format, which stands for “fail u l0ser” (no it doesn’t), as it’s a PJL formatted file that you’re supposed to send to the printer to be able to upgrade it. So it’s not binary – well it is, but it’s a mess of mysterious blocks and whatnot, so it wasn’t going onto the USB key in the original format.
Here’s a dump of the first couple of lines:
00000000 1B 25 2D 31 32 33 34 35 .%-12345
00000008 58 40 50 4A 4C 20 43 4F X@PJL CO
00000010 4D 4D 45 4E 54 20 28 6E MMENT (n
00000018 75 6C 6C 29 0A 40 50 4A ull).@PJ
00000020 4C 20 45 4E 54 45 52 20 L ENTER
00000028 4C 41 4E 47 55 41 47 45 LANGUAGE
00000030 3D 46 57 55 50 44 41 54 =FWUPDAT
00000038 45 0A 1B 45 54 68 69 73 E..EThis
00000040 20 64 65 76 69 63 65 20 device
00000048 64 6F 65 73 20 6E 6F 74 does not
00000050 20 73 75 70 70 6F 72 74 support
00000058 20 46 57 55 50 44 41 54 FWUPDAT
00000060 45 21 0D 0A 1B 2A 72 74 E!...*rt
00000068 31 36 33 38 34 73 41 1B 16384sA.
00000070 2A 62 31 36 35 34 33 59 *b16543Y
00000078 1B 2A 62 2B 30 59 1B 2A .*b+0Y.*
00000080 62 31 36 33 38 34 56 FE b16384V.
00000088 ED F0 0A 02 02 01 B0 00 ........
00000090 04 00 08 01 00 9E 84 25 .......%
00000098 E6 67 30 D7 F3 3B 3B DE .g0..;;.
000000A0 5C 94 48 15 63 C6 39 77 \.H.c.9w
000000A8 01 B1 47 D4 77 28 97 8B ..G.w(..
000000B0 0F A0 DF 92 99 38 D1 B3 .....8..
000000B8 72 93 BE F5 09 A6 E8 25 r......%
000000C0 4D 73 47 37 37 FC 1A B6 MsG77...
000000C8 D0 85 4F 3C F8 05 76 28 ..O<..v(
000000D0 5A A1 F9 2C 2C C8 9F 98 Z..,,...
000000D8 F6 BA C8 91 40 A8 0C 5A ....@..Z
Googling for variations of HP printer firmware, FWUPDATE etc. yielded nothign usable, but quite an interesting read on hacking other HP printers.
So a mysterious format to tinker with – and I had dumps of earlier versions of the firmware (with random corruptions to spice everything up), so perhaps patience and staring would bring a solution.
Lots of staring.
After two nights of patience, I’d reversed the format to my satisfaction and reassembled the firmware from the update. It involved reverse engineering the block format of the update, which involved plain blocks, RLE compressed blocks and 2 blocks of some sort of repeating zeroes compression format that I had to figure out. Fun times!
The code for everything is here – it’s written in Go, which is my favourite programming language of all time:
package main
import (
"bufio"
"errors"
"fmt"
"os"
"strconv"
"strings"
"github.com/davecgh/go-spew/spew"
)
type stateinfo uint8
const (
LookingForESC stateinfo = iota
LookingForStar
LookingForUpperCase
Data
DataSkip
)
type decodemode uint8
const (
Normal decodemode = iota // b
RLE // b2m
Zeroes
)
func main() {
debug := false
f, err := os.Open("amperexl_pr_AXP2CN1829BR_secure_signed_rbx.ful")
if err != nil {
fmt.Printf("Problem opening source file: %v\n", err)
os.Exit(1)
}
defer f.Close()
s := bufio.NewReader(f)
tf, err := os.Create("target.bin")
if err != nil {
fmt.Printf("Problem creating destination file: %v\n", err)
os.Exit(1)
}
defer tf.Close()
t := bufio.NewWriter(tf)
defer t.Flush()
pos := 0
var blocksize, totalsize int
var state stateinfo
var dm decodemode
var datasize, dataleft int
var firstfound, problem bool
var commandpos int
var command string
// var commands int
output := make([]byte, 0, 16384)
var problems, blocks0, blocks1, blocks2, blocks3, blocksp, bytesread int
fmt.Println("Processing ...")
var skipfirst int
if !debug {
// Header
t.Write([]byte{0xFE, 0xED, 0xF0, 0x0D})
skipfirst = 0x10E // This might need correction
}
// var header = make([]byte, 5)
// var expectedheader = []byte{0xFE, 0xED, 0xF0, 0x0A, 0x02}
// s.Read(header)
// if !bytes.Equal(header, expectedheader) {
// fmt.Printf("Wrong header detected - I got %0X, but expected %0X\n", header, expectedheader)
// os.Exit(1)
// }
mainloop:
for {
switch state {
case LookingForESC:
d, err := s.ReadByte()
if err != nil {
fmt.Printf("Problem reading data: %v\n", err)
break mainloop
}
if d == 0x1B {
problem = false
command = ""
state++
} else if firstfound && !problem {
fmt.Print("Not at ESC\n")
problem = true
problems++
}
case LookingForStar:
d, err := s.ReadByte()
if err != nil {
fmt.Printf("Problem reading data: %v\n", err)
break mainloop
}
if d == 0x2A {
commandpos = pos - 1
state++
firstfound = true
} else {
state = LookingForESC
}
case LookingForUpperCase:
d, err := s.ReadByte()
if err != nil {
fmt.Printf("Problem reading data: %v\n", err)
break mainloop
}
command = command + string(d)
if d >= 'A' && d <= 'Z' {
state = LookingForESC
switch {
case strings.HasPrefix(command, "rt"):
blocksize, _, err = getint(command[2:])
if debug {
fmt.Fprintf(t, "%08X: *%v - blocksize set to %v\n", commandpos, command, blocksize)
}
case command == "rC":
if debug {
fmt.Fprintf(t, "%08X: *%v - end of update\n", commandpos, command)
}
break mainloop
case strings.HasPrefix(command, "b+1ym") || strings.HasPrefix(command, "b+2ym"):
dm = Normal
if debug {
fmt.Fprintf(t, "%08X: *%v - checksum\n", commandpos, command)
}
datasize, command, err = getint(command[5:])
if err != nil {
fmt.Printf("Error parsing command, integer not found: %v\n", command)
break mainloop
}
dataleft = datasize
state = DataSkip
case strings.HasPrefix(command, "b+"):
dm = Normal
if debug {
fmt.Fprintf(t, "%08X: *%v - block+\n", commandpos, command)
}
blocksp++
datasize, command, err = getint(command[2:])
if err != nil {
fmt.Printf("Error parsing command, integer not found: %v\n", command)
break mainloop
}
if datasize > 0 {
dataleft = datasize
state = Data
}
case strings.HasPrefix(command, "b"):
rest := command[1:]
switch {
case strings.HasPrefix(rest, "0m"):
fmt.Println("Switching to normal")
dm = Normal
rest = rest[2:]
case strings.HasPrefix(rest, "2m"):
fmt.Println("Switching to RLE")
dm = RLE
rest = rest[2:]
case strings.HasPrefix(rest, "3m"):
fmt.Println("Switching to Zeroes")
dm = Zeroes
// dm = Normal
rest = rest[2:]
}
if debug {
fmt.Fprintf(t, "%08X: *%v - block\n", commandpos, command)
}
blocks1++
datasize, rest, err = getint(rest)
if err != nil {
fmt.Printf("Error parsing command, integer not found: %v\n", command)
break mainloop
}
dataleft = datasize
state = Data
default:
fmt.Printf("%08X: *%v - UNKNOWN\n", commandpos, command)
os.Exit(1)
}
}
case DataSkip:
if dataleft != 0 {
_, err := s.ReadByte()
if err != nil {
fmt.Printf("Problem reading data: %v\n", err)
break mainloop
}
dataleft--
}
if dataleft == 0 {
state = LookingForESC
}
case Data:
if dataleft > 0 {
d, err := s.ReadByte()
if err != nil {
fmt.Printf("Problem reading data: %v\n", err)
break mainloop
}
bytesread++
dataleft--
switch dm {
case Zeroes:
// First three are normal chars
// Next five are zeroes repeated
normals := int(d>>5) + 1
zeroes := int(d & 0x1F)
if zeroes == 0x1F {
for {
morezeroes, _ := s.ReadByte()
zeroes += int(morezeroes)
dataleft--
if morezeroes != 0xFF {
break
}
}
}
for i := 0; i < zeroes; i++ {
output = append(output, 0)
}
for i := 0; i < normals; i++ {
d, _ = s.ReadByte()
dataleft--
output = append(output, d)
}
case Normal:
output = append(output, d)
case RLE:
if d > 0x80 {
// Repeat next character 0xFF - d + 2 times
repeat := 0xFF - int(d) + 2
d, _ = s.ReadByte()
dataleft--
for i := 0; i < repeat; i++ {
output = append(output, d)
}
} else {
// Next d+1 chars are regular
for i := 0; i <= int(d); i++ {
n, _ := s.ReadByte()
dataleft--
output = append(output, n)
}
}
}
}
if dataleft == 0 {
state = LookingForESC
if len(output) != 0 && len(output) != blocksize {
fmt.Printf("Incorrect block size detected: %v\n", len(output))
}
if debug {
spew.Fdump(t, output[:blocksize])
} else {
t.Write(output[skipfirst:blocksize])
skipfirst = 0
}
totalsize += blocksize
output = make([]byte, 0, 16384)
}
}
if state == Data && strings.HasSuffix(command, "Y") {
state = LookingForESC
dataleft = 0
datasize = 0
}
pos++
}
fmt.Printf("Problems: %v - Blocks: %v %v %v %v %v - total %v blocks - %v bytes\n", problems, blocks0, blocks1, blocks2, blocks3, blocksp, blocks0+blocks1+blocks2+blocks3+blocksp, bytesread)
fmt.Printf("Total size: %v\n", totalsize)
}
var NoIntegerFound = errors.New("No integer found")
func getint(s string) (num int, rest string, err error) {
var is string
for len(s) > 0 && s[0] >= '0' && s[0] <= '9' {
is = is + string(s[0])
s = s[1:]
}
if is == "" {
err = NoIntegerFound
return
}
num, err = strconv.Atoi(is)
rest = s
return
}
The firmware update from HP has a signature at the start of the file, beginning with FE ED F0 0A …. the firmware starts from 83 50 00 00, and on the USB key it has to start with FE ED F0 0D (I see what you did there, HP!) so I’m writing the header myself and skipping the signature. You *might* need to adjust this if HP decides to change anything at a later point in time.
My resulting file was 28327670 bytes long, which I kinda-sorta validated by looking at offset 0x00000008 in the file, which contains the value 0x01AFFFF8. The file length is 16 bytes more than that, and comparing various offsets from the corrupted version looked like I got everything wrong.
Exciting, isn’t it? So I dd’ed my manually decoded firmware file to my Kingston drive to both partition1 and partition2. With a smug smile on my face I plugged it in ….. and it booted straight up!

So in conclusion, to fix this kind of problem:
- Get a USB 2.0 flash drive in a size ranging from 2GB to 8GB (my guess)
- Partition it with fdisk, using the fdisk dump from earlier in this post (notice: partion1 starts at sector 63), also I think you can make partition3 the size of the rest of the drive, but I didn’t experiment.
- Download latest firmware for your printer from HP (beware, there are two variants depending on the age/model of your printer) and run it through my program. There are some debug output, don’t worry about this. Check that first bytes in file is FE ED F0 0D 83 50 00 00 and that the next 4 bytes represent the total file size minus 16 bytes.
- Copy the firmware onto partition1 and partition2
- Format partition3 as FAT32

The above fixed my new scrap printer – it might not be universal, but if you know what you’re doing, hopefully this should help you get everything sorted out.
Have fun!
I have the same problem with my DesignJet T520 as you do, can you shoot me over a copy of the amperexl_pr_AXP2CN1829BR_secure_signed_rbx.ful firmware, it looks like HP has removed it from the internet and the new one has different offsets.
Hi Lars,
I have a ipf605 printer that I got to try to ‘fix’ for my sons school. The problem is the eeprom chip on mainboard needs to match another eeprom somewhere else in the printer. It’s stores firmware (which i succesfully flashed) but also serial numbers, numbers of pages printed, date of original install and so on. Somehow all data is zeros (firmware still works, ie printer boots) but it reports and error. Canon wants over £1000 to start investigating the problem which is no go. The question is, there is a chip marked
428M
48H1
01
which is supposed to be 128KB EEPROM. Package is SOIC8.
Is there a way to find out what that chip actually is and how to read it? The idea is for me to find functioning board and then copy onto this one. If I’m in a fantasy land, and the printer is destined for scrapyard, please tell me so 🙂
Regards JL
AXP2CN1829BR here:
https://mnogochernil.ru/newsroom/hp-designjet-t120-t520-firmware-versions/
Hello Lars,
I’ve been trying your method to fix a t520. However, as I’m still not a Linux expert, I have a few doubts:
1. Do partition 1 and partition 2 need to have a file system of their own? If so, what kind of format should they have?
2. How can I copy the firmware files to both partition 1 and 2?
3. Does the target.bin file, output of your Go code, need to be renamed in a specific way? If so, which?
I hope you get to see my message. So far I’ve tried this process in different ways, but to no avail…
Kind Regards.
Hello,
Here is a method that worked for me.:
– i made a clone disk to the usb pendrive, to another 2 Gb Usb pendrive.
– after a few trial and error attempts, I deleted the first partition from the new clone drive, and set the second partitions status as ACTIVE
– when I conected it to the printer it started working right away
The problem is that this way I couldn’t upgrade the firmware.
Finally I found a link under a YT video, that was presenting a way to backup the pendrive, making an image file, that contained an image from a newer version pendrive that had still the (revA) version of fimware + the software it was backed up with. I restored that image with the included software to another pendrive, it worked on first attempt, and was able to upgrade the firmware to the newest possible.
YT video link: https://www.youtube.com/watch?v=o0ue12v78B0
Link to image in description: https://drive.google.com/file/d/1y3Us_RaBAPa4tduZ8BOlh_aJ1PgeE1LR/view
I hope it works for other too
Regards CSZ
Thank you so much! I have fixed my printer. https://github.com/Isanderthul/T120bsodFix
Awesome, thanks for picking this up and releasing some easy to use code 😉
Thanks for tutorial
i succesfully cloned usb sticks, but then i have an erro doing hpfix.go
go run hpfix.go
hpfix.go:11:2: no required module provides package github.com/davecgh/go-spew/spew: go.mod file not found in current directory or any parent directory; see ‘go help modules’
can someone help me?
@Peter I have updated my repo with a fix for the ‘no required module provides package’
Thanks for tutorial.
I have the same problem with T120 – blue screen. My firmware is AXP2CN 1325AR. Can anyone help me find the same one. Thanks in advance.
Hi, is it possible to downgrade firmware on this way? last firmware caused that my cartridhes (with auto reset chip ) don’t work anymore. Many thanks
Very useful! Thanks you!
I have the same problem with T520 – blue screen. My firmware is AXP2CN 1350AR. Can someone help me find the same one?
I bought a chip from ebay which cured my blue screen problem
For the non-techies, HP sells a replacement pre-configured USB key for less than $40.00 Part number CQ890-67105