feat: add reversing scripts/notes
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -105,15 +105,7 @@ The following cameras are currently recognized. Feature support varies per model
|
||||
|
||||
## Help Add Your Camera
|
||||
|
||||
If your camera isn't listed, or a feature is missing, you can help expedite support:
|
||||
|
||||
- Run a detailed device dump and share the log:
|
||||
|
||||
`fujicli device dump -vvv > dump.log 2>&1`
|
||||
|
||||
- Open an issue, or send `dump.log` to the maintainers. This helps map PTP properties and behaviors.
|
||||
|
||||
- Rendering is especially involved and typically needs extra per-camera reverse engineering; dumps are invaluable but additional iteration is expected.
|
||||
If your camera isn't listed, or a feature is missing, you can help expedite support. See [support/REVERSING.md](support/REVERSING.md) for detailed instructions.
|
||||
|
||||
### Emulation Mode (`--emulate`)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ pub enum DeviceCmd {
|
||||
/// Reverse engineer device communication
|
||||
///
|
||||
/// Only run this if you have a full device backup and know what
|
||||
/// you are doing. Misuse can corrupt your camera.
|
||||
/// you are doing. Misuse can corrupt your camera or void your warranty.
|
||||
#[command(alias = "r", subcommand, hide = true)]
|
||||
Reverse(ReverseCmd),
|
||||
}
|
||||
|
||||
@@ -248,6 +248,9 @@ impl PtpSerialize for XT5ConversionProfile {
|
||||
i32::from(self.clarity).try_write_ptp(buf)?;
|
||||
u32::from(self.teleconverter).try_write_ptp(buf)?;
|
||||
|
||||
// Yes, X RAW Studio sends 32 more bits that it never receives. These are typically 0 or 1, but I can't tell what they do.
|
||||
0x0i32.try_write_ptp(buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ impl Ptp {
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn write(
|
||||
&self,
|
||||
kind: ContainerType,
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Reverse Engineering Fujifilm Cameras
|
||||
|
||||
```
|
||||
Reverse engineer device communication
|
||||
|
||||
Only run this if you have a full device backup and know what you are doing. Misuse can corrupt your camera or void your warranty.
|
||||
|
||||
Usage: fujicli device reverse [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
backup Attempt to manage backups
|
||||
info Attempt to get camera info
|
||||
simulation Get information about supported simulation management commands
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-j, --json
|
||||
Format output using json
|
||||
|
||||
-v, --verbose...
|
||||
Log extra debugging information (multiple instances increase verbosity)
|
||||
|
||||
-d, --device <DEVICE>
|
||||
Manually specify target device using USB <BUS>.<ADDRESS>
|
||||
|
||||
--emulate <EMULATE>
|
||||
Treat device as a different model using <VENDOR_ID>:<PRODUCT_ID>
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
- Ensure you have a full device backup.
|
||||
- Run commands with tracing enabled: `-vvv`.
|
||||
|
||||
### Info Support
|
||||
|
||||
- Verify support for info commands by running `fujicli device reverse info`.
|
||||
- Successful output includes a hex dump of the raw PTP response.
|
||||
|
||||
### Backup Support
|
||||
|
||||
- Verify support for backup commands by running `fujicli device reverse backup export <OUTPUT>`.
|
||||
- This should generate a full backup file at `<OUTPUT>`. You can them attempt to import it back to the camera using `fujicli device reverse backup import <INPUT>`.
|
||||
|
||||
### Simulation Support
|
||||
|
||||
- Run `fujicli device reverse simulation` to test support for all known PTP codes and simulation slots.
|
||||
- Errors are not necessarily fatal; some cameras may only support a subset of commands or slots.
|
||||
- Allowed values are not verified, we only read from the device.
|
||||
|
||||
### Rendering Support
|
||||
|
||||
Rendering support is **very complicated** and still largely experimental. Fujifilm does not expose conversion profiles or rendering parameters in any clean or documented way, so this work relies on observing raw USB traffic while official tools manipulate the device.
|
||||
|
||||
At a high level, the goal is to map conversion profile byte offsets by watching how Fujifilm X RAW Studio communicates rendering changes to the camera over PTP.
|
||||
|
||||
My usual setup looks like this:
|
||||
|
||||
- Windows QEMU VM
|
||||
- Used exclusively to run Fujifilm X RAW Studio.
|
||||
- You can create a minimal installation ISO without dealing with Microsoft's bullshit by using [https://schneegans.de/windows/unattend-generator/](https://schneegans.de/windows/unattend-generator/).
|
||||
|
||||
- Wireshark
|
||||
- A patch is required to properly expose `frame.raw` in `tshark` for USB traffic, see [https://git.karaolidis.com/karaolidis/nix/src/branch/main/overlays/wireshark/frame-raw.patch](https://git.karaolidis.com/karaolidis/nix/src/branch/main/overlays/wireshark/frame-raw.patch).
|
||||
|
||||
1. Load the `usbmon` kernel module.
|
||||
2. Run `support/monitor-rendering.sh` *before* connecting the camera, see [https://gitlab.com/wireshark/wireshark/-/issues/20908](https://gitlab.com/wireshark/wireshark/-/issues/20908).
|
||||
3. Start the Windows VM.
|
||||
4. Connect the camera and pass it through directly to the VM.
|
||||
5. Open Fujifilm X RAW Studio.
|
||||
6. Make rendering changes (film simulations, tone curves, etc.). You don't actually need to "finalize" the render, the preview is enough.
|
||||
7. Observe the USB PTP traffic, conversion profile bytes will be printed as they change.
|
||||
|
||||
### EXIF Parsing Support
|
||||
|
||||
TBD - can be automated later using bash scripts that render images via `fujicli` and diff `exiftool` output.
|
||||
|
||||
### Known Pitfalls
|
||||
|
||||
- With at least one camera (X-T5), X RAW Studio is known to receive less conversion profile fields than it sends back. God knows why this happens.
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
PACKET_FILTER='_ws.col.protocol == "USB-PTP" && usb.endpoint_address.direction == "Out"'
|
||||
|
||||
parsing=""
|
||||
profile=""
|
||||
start_tx_id_num=-1
|
||||
|
||||
print_le2() {
|
||||
chunk="$1"
|
||||
c0="${chunk:0:2}"
|
||||
c1="${chunk:2:2}"
|
||||
echo -n "$c1$c0 "
|
||||
}
|
||||
|
||||
print_le4() {
|
||||
chunk="$1"
|
||||
c0="${chunk:0:2}"
|
||||
c1="${chunk:2:2}"
|
||||
c2="${chunk:4:2}"
|
||||
c3="${chunk:6:2}"
|
||||
echo -n "$c3$c2$c1$c0 "
|
||||
}
|
||||
|
||||
print_profile() {
|
||||
local profile="$1"
|
||||
local pos=0
|
||||
|
||||
# Number of fields
|
||||
print_le2 "${profile:pos:4}"
|
||||
pos=$((pos + 4))
|
||||
|
||||
echo -n "- "
|
||||
|
||||
# Identifier length (known)
|
||||
pos=$((pos + 2))
|
||||
|
||||
# Identifier
|
||||
for _ in {0..7}; do
|
||||
print_le2 "${profile:pos:4}"
|
||||
pos=$((pos + 4))
|
||||
done
|
||||
|
||||
# Buffer
|
||||
zeros_len=$((0x1EE * 2))
|
||||
echo -n "[...] "
|
||||
pos=$((pos + zeros_len))
|
||||
|
||||
# Fields
|
||||
while (( pos < ${#profile} )); do
|
||||
print_le4 "${profile:pos:8}"
|
||||
pos=$((pos + 8))
|
||||
done
|
||||
|
||||
echo
|
||||
}
|
||||
|
||||
while IFS=$'|' read -r cont_type op_code tx_id device_prop payload frame; do
|
||||
cont_type=${cont_type:-}
|
||||
op_code=${op_code:-}
|
||||
tx_id=${tx_id:-0}
|
||||
tx_id_num=$((16#${tx_id#0x}))
|
||||
device_prop=${device_prop:-}
|
||||
payload=${payload:-}
|
||||
frame=${frame:-}
|
||||
|
||||
if [[ -n "$parsing" ]] && [[ "$tx_id_num" -eq $((start_tx_id_num + 1)) ]]; then
|
||||
print_profile "$profile"
|
||||
parsing=""
|
||||
profile=""
|
||||
start_tx_id_num=""
|
||||
fi
|
||||
|
||||
if [[ "$op_code" == "0x1016" ]] && [[ "$device_prop" == "0xd185" ]]; then
|
||||
parsing="true"
|
||||
start_tx_id_num=$tx_id_num
|
||||
fi
|
||||
|
||||
if [[ -n "$parsing" ]]; then
|
||||
# Fuji sometimes sends back-to-back packets with an invalid header.
|
||||
# If any of the below are true while parsing, we are hitting this case,
|
||||
# and we need to take the bytes from the "header" and prepend them to the real payload.
|
||||
if [[ ( "$cont_type" != "0x0001" && "$cont_type" != "0x0002" ) || "$op_code" != "0x1016" || "$tx_id_num" -ne "$start_tx_id_num" ]]; then
|
||||
# Take bytes 64-75 of frame (chars 128-151, since 2 chars per byte)
|
||||
frame_part="${frame:128:24}"
|
||||
payload="$frame_part$payload"
|
||||
fi
|
||||
|
||||
profile+="$payload"
|
||||
fi
|
||||
done < <(
|
||||
tshark -Q -l -i "$INTERFACE" \
|
||||
-Y "$PACKET_FILTER" \
|
||||
-T fields \
|
||||
-E separator=\| \
|
||||
-e usb-ptp.container.type \
|
||||
-e usb-ptp.operation.code \
|
||||
-e usb-ptp.transaction.id \
|
||||
-e usb-ptp.device.property \
|
||||
-e usb-ptp.payload \
|
||||
-e frame.raw
|
||||
)
|
||||
@@ -9,6 +9,7 @@
|
||||
};
|
||||
|
||||
rustfmt.enable = true;
|
||||
shellcheck.enable = true;
|
||||
};
|
||||
|
||||
settings.global.excludes = [ ".envrc" ];
|
||||
|
||||
Reference in New Issue
Block a user