A Rust implementation of the Samsung Odin/LOKE download mode protocol. Based on Thor (C#) and odin4 (C), with full feature parity and additional capabilities.
rodin communicates with Samsung devices in download mode (04e8:685d) using the Odin/LOKE protocol over USB bulk transfers. It provides a command-line interface for all standard Odin operations plus advanced features like multi-command sessions, device info dumps, and LZ4 support detection.
- Full Odin protocol: Handshake, session management, PIT operations, flashing, reboot
- Multi-command sessions: Run multiple commands in a single session (Thor-style)
- Flash + reboot: Atomic flash → reboot in one command
- Device info dump: Model, serial, region, carrier
- LZ4 support detection: Detects bootloader LZ4 compression support
- LZ4 decompression: Automatic decompression of
.lz4files - MD5 verification: Verify
tar.md5checksums before flashing - Progress display: Real-time progress with speed and ETA
- Pre-handshake commands: RESET, DVIF (work without session)
- Recovery mechanism: Automatic recovery for stuck bootloader state
cargo build --releaseThe binary is at target/release/rodin.
- Rust toolchain (
rustc,cargo) - USBFS mounted at
/dev/bus/usb - udev rule for Samsung devices:
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", MODE="0666" cdc_acmkernel module unloaded (or device not auto-claimed)
# List all Samsung USB devices
./rodin listEach command opens a session, executes, and closes the session automatically. After execution, the device reboots to Android mode — use adb reboot download to return to download mode.
# Connect and verify handshake
./rodin connect
# Dump and print PIT table
./rodin pit
# Dump device information (model, serial, region, carrier)
./rodin device-info
# Dump PIT to file
./rodin dump-pit pit.bin
# Flash a PIT file to device
./rodin flash-pit pit.bin
# Flash a file (auto-detect partition from filename)
./rodin flash vbmeta.img
# Flash a file to a specific partition
./rodin flash vbmeta.img --partition VBMETA
# Flash a file with LZ4 decompression
./rodin flash vbmeta.img.lz4 --lz4
# Flash a tar archive
./rodin flash-tar package.tar
# Flash a tar.md5 archive (with MD5 verification)
./rodin flash-tar-md5 BL_G991U1.tar.md5
# Erase a partition
./rodin erase vbmeta 65536
# Factory reset (erase userdata)
./rodin factory-reset
# Reboot device
./rodin reboot
# Reboot to download mode (reboots to Android, use adb reboot download)
./rodin reboot-odin
# Shutdown device
./rodin shutdownFlash a file and reboot in a single atomic operation:
./rodin flash-reboot vbmeta.img --partition VBMETARun multiple commands in a single session (Thor-style). The session stays open across all commands, avoiding the bootloader stuck state:
# Dump device info and PIT in one session
./rodin session -c device-info -c pit
# Flash and reboot in one session
./rodin session -c "flash?file=vbmeta.img&partition=VBMETA" -c reboot-odin
# Flash tar and reboot
./rodin session -c "flash-tar?tar=package.tar" -c reboot
# Multiple commands with options
./rodin session \
-c "option?name=efsclear&value=true" \
-c "flash?file=vbmeta.img&partition=VBMETA" \
-c reboot-odinSession action formats:
| Action | Format |
|---|---|
pit |
pit |
device-info |
device-info |
dump-pit |
dump-pit?output=pit.bin |
flash |
flash?file=path&partition=name&lz4=true |
flash-tar |
flash-tar?tar=path&lz4=true |
flash-tar-md5 |
flash-tar-md5?tar=path&lz4=true |
erase |
erase?partition=name&size=65536 |
factory-reset |
factory-reset |
reboot |
reboot |
reboot-odin |
reboot-odin |
shutdown |
shutdown |
option |
option?name=efsclear&value=true |
set-region |
set-region?code=XYZ |
# Set EFS clear
./rodin options efsclear true
# Set bootloader update
./rodin options blupdate true
# Enable T-Flash
./rodin options tflash true
# Disable reset flash count
./rodin options resetfc false
# Show current value
./rodin options efsclear# Set 3-character region code
./rodin set-region XYZ# Raw write (string)
./rodin raw-write string "ODIN"
# Raw write (integers)
./rodin raw-write int 0x64 0x00
# Raw write (hex bytes)
./rodin raw-write bytes 64 00 00 00
# Raw read
./rodin raw-read 1024These commands work without a session (before ODIN/LOKE handshake):
# Send RESET command (device may reboot)
./rodin pre-cmd reset
# Send DVIF command (device info)
./rodin pre-cmd dvifAll commands accept an optional --device flag to specify the USB device path:
./rodin flash vbmeta.img --device /dev/bus/usb/003/009If omitted, the first Samsung device found is used.
Open USB → Claim Interface → Clear Halt → Handshake (ODIN/LOKE)
→ BeginSession (0x64/0x00) → SendFilePartSize (0x64/0x05, v2+)
→ Commands (PIT, Flash, Options, etc.)
→ EndSession (0x67/0x00) → RebootToOdin (0x67/0x02)
→ Release Interface → Close FD
Session cleanup: After each command, EndSession + RebootToOdin is sent automatically via Drop. This properly closes the session on the bootloader side and reboots the device to Android mode. Use adb reboot download to return to download mode.
Terminal commands (reboot, reboot-odin, shutdown) call EndSession internally before sending the reboot command.
Multi-command sessions (session) keep the session open across all commands. EndSession + RebootToOdin is sent when the session ends (or when a terminal command is executed).
The Odin/LOKE protocol uses USB bulk transfers on the CDC-Data interface (class 0x0a). All commands are sent as 1024-byte packets with a 4-byte region code and 4-byte sub-command at the start. Responses are 8 bytes: [0x00..0xFE, 0x00, 0x00, 0x00, <data...>] on success, or [0xFF, 0x00, 0x00, 0x00, <error_code>] on failure.
- Host sends
ODIN(4 bytes) via bulk write - Device responds with
LOKE(4 bytes) via bulk read
| Sub-command | Name | Description |
|---|---|---|
| 0x00 | BeginSession | Start session, returns bootloader version |
| 0x01 | ResetFlashCount | Reset flash counter |
| 0x02 | SetTotalBytes | Set total bytes to flash (8-byte LE value at offset 8) |
| 0x05 | SetPacketSize | Set packet size (4-byte LE value at offset 8) |
| 0x07 | EraseUserData | Erase userdata partition (factory reset) |
| 0x08 | EnableTFlash | Enable T-Flash mode |
| 0x08 | SetRegionCode | Set region code (3-char ASCII at offset 8) |
| Sub-command | Name | Description |
|---|---|---|
| 0x00 | RequestFileFlash | Initialize file flash operation |
| 0x02 | RequestSequence | Request sequence flash (4-byte LE size at offset 8) |
| 0x03 | EndSequence | Complete sequence flash |
EndSequence payload (offset 8+):
- Bytes 8-11: 0x00 (phone firmware) or 0x01 (modem firmware)
- Bytes 12-15: Real data size (LE)
- Bytes 16-19: Binary type (from PIT)
- Bytes 20-23: Device type (from PIT)
- Bytes 24-27: Partition ID (from PIT, phone only)
- Bytes 28-31: Is last sequence (0 or 1, phone only)
- Bytes 32-35: EFS clear (0 or 1, phone only)
- Bytes 36-39: Bootloader update (0 or 1, phone only)
| Sub-command | Name | Description |
|---|---|---|
| 0x00 | RequestPitFlash | Initialize PIT flash |
| 0x01 | RequestPitDump | Request PIT dump, returns size |
| 0x02 | ReadPitBlock | Read 500-byte block (block index at offset 8) |
| 0x02 | BeginPitFlash | Begin PIT flash (size at offset 8) |
| 0x03 | EndPitDump | Complete PIT dump |
| 0x03 | EndPitFlash | Complete PIT flash |
| Sub-command | Name | Description |
|---|---|---|
| 0x00 | RequestDevInfo | Request device info dump, returns size |
| 0x01 | ReadDevInfoBlock | Read 500-byte block (block index at offset 8) |
| 0x02 | EndDevInfo | Complete device info dump |
Device info response format:
- Header: magic (0x12345678), entry count, location array, data array
- Location entry: type (4), offset (4), size (4)
- Data entry: type (4), size (4), raw data
- Types: 0x00=Model, 0x01=Serial, 0x02=Region Code, 0x03=Carrier ID
| Sub-command | Name | Description |
|---|---|---|
| 0x00 | EndSession | End session |
| 0x01 | Reboot | Reboot device |
| 0x02 | RebootToOdin | Reboot to download mode (reboots to Android) |
| 0x03 | Shutdown | Shutdown device |
| Code | Meaning |
|---|---|
| -2 | Write Protect |
| -3 | Erase |
| -4 | Write |
| -5 | Auth |
| -6 | Size |
| -7 | Ext4 |
rodin
├── USB Device Layer (usb.rs)
│ ├── Device discovery (scans /dev/bus/usb)
│ ├── Descriptor parsing (finds CDC-Data interface)
│ ├── Interface claiming (ioctl USBDEVFS_CLAIMINTERFACE)
│ ├── Bulk write/read (ioctl USBDEVFS_BULK)
│ ├── Control transfer (ioctl USBDEVFS_CONTROL)
│ └── Clear halt (ioctl USBDEVFS_CLEAR_HALT)
│
├── Odin Protocol Layer (odin.rs)
│ ├── Handshake (ODIN/LOKE)
│ ├── Session management (begin/end)
│ ├── LZ4 support detection (bit 0x8000 of version)
│ ├── Device info dump (model, serial, region, carrier)
│ ├── PIT dump/parse/flash
│ ├── File flashing (sequences, packets, progress)
│ ├── Partition erase (zero-fill flash)
│ ├── Factory reset (erase userdata)
│ ├── Flash options (EFS clear, BL update, T-Flash)
│ ├── Region code (set region)
│ ├── Raw I/O (raw write/read)
│ ├── LZ4 decompression (automatic for .lz4 files)
│ ├── MD5 verification (tar.md5 packages)
│ ├── Reboot/shutdown
│ └── Recovery (EndSession + RebootToOdin)
│
├── PIT Parser (pit.rs)
│ ├── Magic number validation (0x12349876)
│ ├── Entry parsing (partition info)
│ └── Lookup by filename/partition
│
├── Progress Tracking (progress.rs)
│ ├── Real-time progress display
│ ├── Transfer speed calculation
│ └── ETA estimation
│
└── CLI (main.rs)
├── list, connect, device-info, pit, dump-pit, flash-pit
├── flash, flash-tar, flash-tar-md5, flash-reboot
├── erase, factory-reset, options, set-region
├── raw-write, raw-read, reboot, reboot-odin, shutdown
├── session (multi-command, Thor-style)
└── pre-cmd (pre-handshake: reset, dvif)
| Feature | Thor | odin4 | rodin |
|---|---|---|---|
| Handshake (ODIN/LOKE) | ✅ | ✅ | ✅ |
| Session management | ✅ | ✅ | ✅ |
| PIT dump/print | ✅ | ✅ | ✅ |
| PIT dump to file | ✅ | ❌ | ✅ |
| PIT flash | ✅ | ❌ | ✅ |
| Flash file | ✅ | ✅ | ✅ |
| Flash tar | ✅ | ✅ | ✅ |
| Flash tar.md5 | ✅ | ✅ | ✅ |
| LZ4 decompression | ✅ | ✅ | ✅ |
| MD5 verification | ❌ | ✅ | ✅ |
| Erase partition | ✅ | ✅ | ✅ |
| Factory reset | ✅ | ❌ | ✅ |
| Flash options | ✅ | ✅ | ✅ |
| EFS clear | ✅ | ✅ | ✅ |
| Bootloader update | ✅ | ✅ | ✅ |
| T-Flash | ✅ | ❌ | ✅ |
| Reset flash count | ✅ | ❌ | ✅ |
| Set region code | ✅ | ❌ | ✅ |
| Raw write | ✅ | ❌ | ✅ |
| Raw read | ✅ | ❌ | ✅ |
| Progress display | ✅ | ✅ | ✅ |
| Reboot | ✅ | ✅ | ✅ |
| Reboot to download mode | ✅ | ✅ | ✅ |
| Flash + reboot (single session) | ❌ | ❌ | ✅ |
| Multi-command session (Thor-style) | ✅ | ❌ | ✅ |
| Shutdown | ❌ | ❌ | ✅ |
| MODEM EndSequence layout | ✅ | ✅ | ✅ |
| EraseUserData timeout (10 min) | ✅ | ✅ | ✅ |
| Device listing | ❌ | ✅ | ✅ |
| Device info dump | ❌ | ❌ | ✅ |
| LZ4 support detection | implicit | ❌ | ✅ |
| Pre-handshake commands (RESET, DVIF) | ❌ | ❌ | ✅ |
| Automatic session cleanup | implicit | ❌ | ✅ |
| Stuck bootloader recovery | ❌ | ❌ | ✅ |
rodin uses Linux USBFS ioctls directly. On 64-bit systems, usbdevfs_bulktransfer and usbdevfs_ctrltransfer are 24 bytes due to pointer alignment padding:
struct usbdevfs_bulktransfer {
__u32 ep; // offset 0
__u32 len; // offset 4
__u32 timeout; // offset 8
__u32 actual_length; // offset 12 (kernel returns via ioctl retval)
void *data; // offset 16 (8-byte aligned, 4 bytes padding before)
};
// Total: 24 bytes (not 20)rodin includes compile-time assertions to prevent future drift:
assert!(std::mem::size_of::<usb_bulk_transfer>() == 24);
assert!(std::mem::size_of::<usb_control_transfer>() == 24);A companion Python script is available at ../usbfs_test.py for low-level USBFS testing:
# Get device descriptor
python3 usbfs_test.py /dev/bus/usb/003/009 get-descriptor
# ODIN/LOKE handshake
python3 usbfs_test.py /dev/bus/usb/003/009 handshake
# Full session (handshake + begin + end + reboot)
python3 usbfs_test.py /dev/bus/usb/003/009 full
# Pre-handshake commands
python3 usbfs_test.py /dev/bus/usb/003/009 dvif
python3 usbfs_test.py /dev/bus/usb/003/009 reset-cmdMIT