Skip to main content

picorescue

Find, inspect and recover data from Raspberry Pi Pico (RP2040 / RP2350) flash dumps. Built for Pimoroni-style images that pair a MicroPython firmware with LittleFS, FAT and (on RP2350) ROMFS partitions, where the rescue target is usually a handful of accidentally-deleted Python scripts.

It reads the bi_decl binary info region (the same BlockDevice declarations parsed by py_decl) to locate partitions, then reads and recovers data from them. It also signature-scans the whole dump and merges the results, so it finds filesystems that are missing from bi_decl or nested inside a declared device - e.g. the dir2uf2 --fs-reserve hybrid where a LittleFS sits in the tail of a FAT block device.

Install / run

Uses uv:

uv sync
uv run picorescue --help

or, from pip:

uv pip install picorescue
uv run picorescue --help

Accepts a raw flash dump (.bin, offset 0 = flash base 0x10000000) or a .uf2 (reassembled to the correct flash addresses, gaps filled with 0xFF).

A full-flash dump is ideal. You can grab one with picotool save -a flash.bin or over SWD with OpenOCD.

Commands

picorescue info       DUMP                 # bi_decl info + partitions, with used storage
picorescue partitions DUMP                 # just the partition table
picorescue debug      DUMP                 # report overlapping filesystems & other errors
picorescue ls         DUMP [-p NAME] [-v]  # list live files (-v adds file count + used size)
picorescue extract    DUMP OUTDIR          # extract live (non-deleted) files
picorescue recover    DUMP OUTDIR          # recover deleted / orphaned data

recover options: -p/--partition NAME, --no-carve, --whole-dump (carve the entire image, not just known partitions), --min-score (carver threshold).

Supported filesystems: LittleFS (list, extract, deleted-file recovery), FAT12/16/32 (list, extract, 0xE5 undelete), and MicroPython ROMFS (list, extract; read-only, with .mpy bytecode surfaced verbatim).

Examples

File names below are illustrative; the layout and figures match real tool output.

Inspect a dump - firmware info, discovered partitions, and estimated usage:

$ picorescue info flash.bin
Loaded flash.bin: 16.00MB @ base 0x10000000
  ProgramName: MicroPython
  ProgramVersion: v1.25.0
  PicoBoard: pico2
  SDKVersion: 2.1.1
  BinaryEndAddress: 0x100d5138

Partitions (3):
  ROMFS                            0x10200000     1.00MB  romfs     [read] (bi_decl)        used ~ 409.7KB ( 40%)
  MicroPython                      0x10300000    13.00MB  fat       [read,write] (bi_decl)  used ~  1.33MB ( 11%)
      12.02MB FAT volume within the 13.00MB MicroPython bi_decl region
  MicroPython:littlefs@0x10f00000  0x10f00000     1.00MB  littlefs  [-] (scan)              used ~ 100.0KB ( 10%)

List a filesystem's live files, with a summary line (-v):

$ picorescue ls flash.bin -p MicroPython:littlefs@0x10f00000 -v

# MicroPython:littlefs@0x10f00000 (littlefs) @ 0x10f00000
     6212  /main.py
      512  /lib/display.py
       78  /state/clock.json
   307200  /state/screenshot.txt
     ... (12 files)
  -- 12 file(s), used ~100.0KB (10% of 1.00MB)

Recover deleted and orphaned data into ./rescued/ (writes a MANIFEST.json):

$ picorescue recover flash.bin ./rescued

# MicroPython (fat) @ 0x10300000 - 13.00MB
  FAT12: undeleted 4 file(s)
  carved 21 Python candidate(s)

# MicroPython:littlefs@0x10f00000 (littlefs) @ 0x10f00000 - 1.00MB
  metadata: 12 inline file(s) recovered (1 not in live FS -> likely deleted); 3 name-only hint(s)
  carved 0 Python candidate(s)

Done. 37 item(s) -> ./rescued (see MANIFEST.json)

$ ls ./rescued/MicroPython/undelete/
config.py  main.py  notes.txt  _ecret.py

Check a dump for structural problems (exits non-zero when errors are found):

$ picorescue debug flash.bin
Loaded flash.bin: 16.00MB @ base 0x10000000
Discovered 3 partition(s).

  [ERROR] Overlap: 'MicroPython' (FAT volume 0x10300000-0x10f04000) and 'MicroPython:littlefs@0x10f00000' (LittleFS 0x10f00000-0x11000000) overlap by 16.0KB

1 issue(s): 1 error(s), 0 warning(s), 0 note(s).

How recovery works

recover writes into OUTDIR/<partition>/ and a top-level MANIFEST.json describing every recovered item and the method used.

  • LittleFS metadata (deleted/, metadata/) - LittleFS is a copy-on-write, log-structured filesystem. Deleting or overwriting a file leaves the old commit (its name and, for small files, its inline content) in the metadata block until that block is erased and compacted. picorescue threads the XOR-delta tag log of every metadata block and pulls out names + inline data across all commits, not just the live view. Entries whose name is absent from the mounted filesystem are flagged as likely-deleted and sorted into deleted/.
  • FAT undelete (undelete/) - deletion only sets the directory entry's first name byte to 0xE5 and frees the cluster chain; the starting cluster and size remain, so contiguous files undelete cleanly. Verify integrity of anything recovered this way - fragmented files may be partial.
  • ROMFS (files/) - ROMFS is immutable, so there are no deletions to hunt; recover simply extracts every file (including .mpy bytecode) alongside the carve pass.
  • Carving (carved/) - filesystem-agnostic. Scans raw blocks for printable text runs and scores them for "Python-ness" (import/def/class, indented multi-line structure, assignments). Catches scripts whose directory entry or metadata is gone but whose content still lingers in flash. Carved content is de-duplicated against live files.

Recovered scripts are best-effort: always eyeball them. Carved fragments may have a stray leading byte or be truncated at a block boundary.

Layout

src/picorescue/
  bidecl.py   vendored py_decl bi_decl parser (BlockDevice discovery)
  dump.py     load .bin/.uf2 -> addressed flash image; partition discovery
  lfs.py      LittleFS mount/list/extract + metadata-log recovery
  fat.py      FAT12/16/32 read + 0xE5 undelete
  romfs.py    MicroPython ROMFS read (list/extract)
  carve.py    Python-source carver
  cli.py      click CLI

Support Me

I work on Pico shinies by day, occasionally cranking out balmy tools to make my job easier and sharing them with the world.

If they help you too, great! If you want to throw me a bone for my troubles, see below:

Find some of my other projects below:

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

picorescue-0.2.0.tar.gz (24.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

picorescue-0.2.0-py3-none-any.whl (28.1 kB view details)

Uploaded Python 3

File details

Details for the file picorescue-0.2.0.tar.gz.

File metadata

  • Download URL: picorescue-0.2.0.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for picorescue-0.2.0.tar.gz
Algorithm Hash digest
SHA256 cadc7e8730bfb7dca89d871caba3923045482f35971f804cf355d0ff65f323c5
MD5 5037158a654d9fa926b519c71213a86d
BLAKE2b-256 4db52e6a022806b16f2ee0552f88d4d5f8809ae52f8bb4ded20ae7f2ac088c86

See more details on using hashes here.

File details

Details for the file picorescue-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: picorescue-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 28.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for picorescue-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a6ad4e8e04b5517e643bec4f6a9b8162e965133f02769ebd5d60af17523bb9bc
MD5 60fc5022460845e83ef60b919edb3b3e
BLAKE2b-256 67c8280e549f5c6696f642b8316e4938695497723fe69d75cae8ef8a77c7974a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page