fileformat

package
v0.405.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package fileformat gives the PromptZero agent structural access to the Flipper file formats it already ships with — .sub, .nfc, .ir, .rfid. Raw `storage read` gives the LLM one giant string; these parsers surface the individual fields + blocks so the model can reason about them (change a frequency, blank a block, rename a signal) without string manipulation.

Every format follows the same shape:

  • Parse<T>(data []byte) (*T, error) — tolerant line-oriented parser.
  • (*T).Marshal() []byte — canonical serializer; round-trip equal under Parse(Marshal(Parse(x))) but not guaranteed byte-for-byte identical to the input.
  • apply<T>Edits(*T, map[string]interface{}) error — validates and applies a top-level edit map; unknown keys fail loudly so the LLM cannot silently no-op.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyEdits

func ApplyEdits(format Format, model any, edits map[string]interface{}) error

ApplyEdits dispatches the edit map to the format-specific applier. Unknown edit keys return an error — never silently ignored.

func BuildIR added in v0.3.0

func BuildIR(p IRBuildParams) ([]byte, error)

BuildIR constructs a canonical .ir remote file. The IRSignal struct is shared with the parser, so callers can assemble a file programmatically and trust the round-trip.

func BuildMousejackPayload added in v0.3.1

func BuildMousejackPayload(p MousejackPayloadParams) ([]byte, error)

BuildMousejackPayload validates the DuckyScript and returns the canonical bytes ready to write to /ext/mousejacker/<name>.txt. Validation rules:

  • non-empty after comment stripping
  • every DELAY argument ≤ MaxDelayMS
  • a sane target-OS string (for future per-OS transformations)

BuildMousejackPayload does not enforce DuckyScript syntax beyond the delay cap — the validator.Validate() pass (called separately on the bytes) handles destructive-pattern detection mirrored from BadUSB.

func BuildNFC added in v0.3.0

func BuildNFC(p NFCBuildParams) ([]byte, error)

BuildNFC constructs a canonical .nfc capture. The resulting file is suitable for nfc_emulate.

UID byte-length is validated against DeviceType so a 4-byte UID paired with "NTAG215" doesn't silently produce a file that would fail every reader probe. Allowed lengths per type follow the published ISO/IEC 14443 tag-family specs:

Mifare Classic 1K/4K/Mini          4 or 7 bytes
Mifare Ultralight / NTAG21x        7 bytes
NTAG215 / NTAG216 / NTAG213        7 bytes
Other / unknown                    any non-empty hex passes (permissive)

func BuildRFID added in v0.3.0

func BuildRFID(p RFIDBuildParams) ([]byte, error)

BuildRFID constructs a canonical .rfid file. The resulting file is suitable for writing with the rfid_write tool to clone onto a T5577 blank.

func BuildSub added in v0.3.0

func BuildSub(p SubBuildParams) ([]byte, error)

BuildSub constructs a canonical .sub capture from parameters. Required: Frequency. Returns the file bytes; callers typically hand the result to Flipper.WriteFileCtx.

func BuildSubBruteforce added in v0.3.0

func BuildSubBruteforce(p SubBruteforceParams) ([]byte, error)

BuildSubBruteforce constructs a RAW .sub file that sweeps the integer key space [StartKey, EndKey] at BitCount bits. Each key is encoded MSB-first using Princeton-style OOK timing: a '1' bit as +3*TE / -1*TE, a '0' bit as +1*TE / -3*TE. A +1*TE / -31*TE sync gap separates adjacent keys — matches the pattern a PT2240 / SC5262 family decoder expects.

The tool is agnostic about which protocol actually authorises the target; it just produces a replayable sweep the operator can feed to subghz_transmit. Per-protocol encoding variants (Keeloq rolling codes, CAME 12-bit, Chamberlain) are not modelled — those need protocol-specific state machines and belong in a later enhancement.

func BuildSubBruteforceSweep added in v0.3.1

func BuildSubBruteforceSweep(p SubFreqSweepParams) (map[uint32][]byte, error)

BuildSubBruteforceSweep generates one .sub file per frequency in p.Frequencies, each covering the same key range. This is the "multi-band bruteforce" workflow the RF specialist audit flagged as missing — garage-door reconnaissance often cycles the same Princeton-family key space across 315 MHz, 433.92 MHz, 868 MHz, and 915 MHz before narrowing to one band.

Returns a map of frequency → raw bytes so the caller can choose its own filename scheme (typically sweep_<freq>.sub). All validation runs per-frequency; a single invalid entry fails the whole call rather than writing some files and leaving others unbuilt.

func FreqmanModulationForPreset added in v0.52.0

func FreqmanModulationForPreset(preset string) string

FreqmanModulationForPreset is the inverse mapping: Flipper preset → Freqman modulation. Returns empty when unknown.

func FreqmanPresetForModulation added in v0.52.0

func FreqmanPresetForModulation(mod string) string

FreqmanPresetForModulation maps a Freqman modulation name to a Flipper `Preset:` header value. Best-effort: returns the empty string when no canonical mapping exists, leaving the caller to fall back to its own default (typically the firmware's `FuriHalSubGhzPresetOok650Async`).

The mappings cover the modulations actually in use across PortaPack and the Flipper community lists; exotic forks can pass through Modulation verbatim and let the firmware reject unknown presets.

func SaveFile

func SaveFile(format Format, model any) ([]byte, error)

SaveFile serializes a previously-parsed model back to bytes.

Types

type DiffEntry

type DiffEntry struct {
	Field  string `json:"field"`
	AField string `json:"a"`
	BField string `json:"b"`
	Same   bool   `json:"same"`
}

DiffEntry is one field-level difference between two parsed files. AField / BField hold the rendered values; Same is true iff they match.

type DiffResult

type DiffResult struct {
	FormatA    Format      `json:"format_a"`
	FormatB    Format      `json:"format_b"`
	SameFormat bool        `json:"same_format"`
	Entries    []DiffEntry `json:"entries"`
}

DiffResult is the structural comparison of two parsed models. Format mismatches surface as SameFormat=false and an empty Entries slice.

func Diff

func Diff(aFormat Format, a any, bFormat Format, b any) (*DiffResult, error)

Diff compares two previously parsed models and returns per-field differences. Only the intersection of fields defined for a format is inspected — block/signal collections are expanded so the caller can see per-index changes. Format mismatches short-circuit with SameFormat=false.

type Format

type Format string

Format identifies one of the four supported file formats. Returned by DetectFormat and LoadFile so callers don't have to re-sniff extensions.

const (
	FormatSub  Format = "sub"
	FormatNFC  Format = "nfc"
	FormatIR   Format = "ir"
	FormatRFID Format = "rfid"
)

func DetectFormat

func DetectFormat(path string) (Format, error)

DetectFormat inspects path's extension and returns the matching Format. Case-insensitive.

func LoadFile

func LoadFile(path string, raw []byte) (any, Format, error)

LoadFile parses raw bytes based on path's extension and returns one of *SubFile / *NFCFile / *IRFile / *RFIDFile, plus the detected format. Unknown extensions yield an error so callers can stay strict.

type FreqmanEntry added in v0.52.0

type FreqmanEntry struct {
	Frequency   uint64
	RangeStart  uint64
	RangeEnd    uint64
	Modulation  string
	Bandwidth   string
	Step        string
	Description string
	Extra       map[string]string
}

FreqmanEntry is one row of a Freqman / PortaPack-Mayhem signal list.

Freqman is the de-facto interop format shared between HackRF/PortaPack- Mayhem, OpenSDR, and several Flipper community tools. Each entry is a single comma-separated `key=value` line. There are two shapes:

  • **single-frequency**: Frequency != 0, RangeStart == 0, RangeEnd == 0. Encoded as `f=<Hz>,m=<mod>,bw=<n>,s=<step>,d=<desc>`.
  • **range scan**: RangeStart != 0 && RangeEnd != 0, Frequency == 0. Encoded as `a=<startHz>,b=<endHz>,m=<mod>,bw=<n>,s=<step>,d=<desc>`.

All non-frequency fields are optional. Bandwidth and Step are preserved as strings (rather than numerics) because the upstream format mixes raw Hz, kHz suffixes, and named presets ("AM_DSB_5KHZ") in different forks; we keep what the file says verbatim so a round-trip is exact.

Extra holds any `key=value` pairs we don't model (tone=, p=, etc.) so a firmware-fork extension survives Parse → Marshal unchanged.

func FreqmanFromSub added in v0.52.0

func FreqmanFromSub(sub *SubFile, description string) (*FreqmanEntry, error)

FreqmanFromSub builds a FreqmanEntry from a Flipper .sub file. Only the frequency, preset, and (caller-supplied) description are carried — the per-protocol fields (Key, TE, RAW_Data) are intentionally not surfaced because Freqman is a *catalogue* format, not a capture format.

Returns an error if sub is nil or its Frequency is zero (a Freqman entry without a frequency is meaningless).

func (FreqmanEntry) IsRange added in v0.52.0

func (e FreqmanEntry) IsRange() bool

IsRange reports whether this entry is a range-scan entry.

func (FreqmanEntry) ToSubLite added in v0.52.0

func (e FreqmanEntry) ToSubLite() (*SubFile, error)

ToSubLite returns a minimal *SubFile that represents this Freqman entry as a Flipper Sub-GHz key file. RAW_Data and protocol-specific fields are not populated — Freqman doesn't carry them. The caller can layer those in if it later captures real RF for the entry.

Range entries cannot be expressed as a single .sub and yield an error.

type FreqmanList added in v0.52.0

type FreqmanList struct {
	Entries []FreqmanEntry
}

FreqmanList is an ordered sequence of FreqmanEntry rows. The order matters: PortaPack's frequency-list browser presents entries in file order, so reorder-on-parse would surprise operators.

func ParseFreqman added in v0.52.0

func ParseFreqman(data []byte) (*FreqmanList, error)

ParseFreqman parses a Freqman list. Tolerant of CRLF, blank lines, and `#` comment lines. Each non-empty, non-comment line MUST contain at least one of `f=` or `a=`+`b=` — otherwise the line is rejected so a malformed file fails at load rather than silently dropping signals.

Parsing rules:

  • The line is split on commas into tokens.
  • Each token is `key=value`. The first `=` is the separator; remaining `=` stay inside value (e.g. base64-encoded Extra fields).
  • Within the *value* of `d=` (description), commas are kept verbatim by treating `d=` as a sticky tail: once we see `d=`, everything after it on the line — commas included — is the description. This mirrors Mayhem's own emitter, which does not quote.
  • Unknown keys go into Extra so round-trip is lossless.

func (*FreqmanList) Find added in v0.52.0

func (l *FreqmanList) Find(query string) *FreqmanEntry

Find returns the first entry whose Description equals desc (case- insensitive) or matches its frequency exactly. Useful for the eventual signal_library_search tool. Returns nil when no match.

func (*FreqmanList) Marshal added in v0.52.0

func (l *FreqmanList) Marshal() []byte

Marshal serialises the list back to canonical Freqman bytes. Field emission order per entry is: f or (a,b), m, bw, s, sorted Extra, d. The description is emitted last because of the sticky-tail rule.

func (*FreqmanList) Sort added in v0.52.0

func (l *FreqmanList) Sort()

Sort orders entries by frequency (single first, then range by start). Stable on tie so the operator's original order survives within a band.

type FreqmanMatch added in v0.52.0

type FreqmanMatch struct {
	File  string       `json:"file"`
	Line  int          `json:"line"` // 1-based, matches editor convention.
	Entry FreqmanEntry `json:"entry"`
}

FreqmanMatch is one hit returned by SearchFreqmanDir / FilterEntries. File and Line locate the entry within the on-disk library so an operator-facing report can render an actionable pointer back into a firmware-fork's editor.

func SearchFreqmanDir added in v0.52.0

func SearchFreqmanDir(root, query string, limit int) ([]FreqmanMatch, []error)

SearchFreqmanDir walks root recursively, parses every `*.txt` file as a Freqman list, and returns matches whose Frequency, RangeStart..RangeEnd band, or Description matches the query.

Match rules (case-insensitive on description):

  • Pure-numeric query: parsed as Hz. Single-frequency entries match on equality. Range entries match when the query Hz falls inside [RangeStart, RangeEnd] inclusive.
  • Otherwise: substring match against Description.

Files that fail to parse are skipped silently (returned in the optional errs slice for the caller's diagnostics) — a single malformed library shouldn't blank the whole result set. If limit > 0, results are capped at limit; the walk stops early once the cap is hit. A non-existent root is not an error and yields zero matches.

All file accesses must remain inside root (filepath.Walk handles that natively for non-symlinked trees; symlinks are followed only when they resolve back inside root, mirroring the snapshot package's policy).

type IRBuildParams added in v0.3.0

type IRBuildParams struct {
	// Name is a display label for the remote. Optional — defaults to
	// "generated".
	Name string

	// Signals is the ordered list of IR entries. Each must have a
	// Name plus either parsed fields or raw fields populated.
	Signals []IRSignal
}

IRBuildParams carries inputs for BuildIR. A valid IR file needs at least one signal; parsed-type signals require Protocol + Address + Command; raw-type signals require Frequency + DutyCycle + Data.

type IRFile

type IRFile struct {
	Filetype string
	Version  int
	Signals  []IRSignal
}

IRFile is a parsed .ir universal-remote / capture file — zero or more signals separated by "#" marker lines.

func ParseIR

func ParseIR(data []byte) (*IRFile, error)

ParseIR parses a Flipper .ir remote/library file. Accepts CRLF, LF, a missing final newline, blank lines, and treats leading "#" lines as signal separators.

func (*IRFile) Marshal

func (f *IRFile) Marshal() []byte

Marshal serializes f back to canonical .ir bytes. Header lines first, then one "#"-separated block per signal. Parsed signals emit name/type/protocol/address/command; raw signals emit name/type/frequency/duty_cycle/data.

type IRSignal

type IRSignal struct {
	Name      string
	Type      string
	Protocol  string
	Address   string
	Command   string
	Frequency int
	DutyCycle float64
	Data      []int
}

IRSignal is one button entry in a Flipper .ir remote file. Parsed-type entries carry Protocol/Address/Command; raw-type entries carry Frequency, DutyCycle, and a Data timing list (microseconds).

type MousejackPayloadParams added in v0.3.1

type MousejackPayloadParams struct {
	// Script is the DuckyScript body. Lines are trimmed and
	// empty/comment lines dropped. Whitespace-only input errors out.
	Script string

	// TargetOS hints the builder at expected key-combo conventions.
	// Valid: "windows", "macos", "linux". Empty defaults to windows.
	TargetOS string

	// MaxDelayMS caps the argument to any DELAY line. Mousejack
	// sessions are 2.4 GHz and flaky — very long delays often lose
	// sync with the receiver. Defaults to 5000 (5s); passing 0
	// applies the default.
	MaxDelayMS int
}

MousejackPayloadParams carries inputs for BuildMousejackPayload. The script is a DuckyScript body — lines of STRING / DELAY / GUI combos etc. that the Mouse Jacker FAP replays at the remote keyboard.

type NFCBuildParams added in v0.3.0

type NFCBuildParams struct {
	// DeviceType is one of "Mifare Classic", "Mifare Ultralight",
	// "NTAG213", "NTAG215", "NTAG216", etc. Accepted verbatim.
	DeviceType string

	// UID hex, e.g. "AA BB CC DD". Spaces between bytes are tolerated.
	UID string

	// ATQA / SAK are the ISO/IEC 14443 response bytes. Optional —
	// omitted for NTAG variants that don't expose them.
	ATQA string
	SAK  string

	// MifareType (e.g. "1K" / "4K") for Classic captures.
	MifareType string

	// Blocks maps block index → space-separated hex bytes. Optional
	// — a bare UID capture with no Blocks still produces a valid
	// file useful for UID-only emulation.
	Blocks map[int]string
}

NFCBuildParams carries inputs for BuildNFC. DeviceType + UID are the minimum; for Mifare Classic the caller typically supplies ATQA, SAK, and a map of block contents.

type NFCFile

type NFCFile struct {
	Filetype   string
	Version    int
	DeviceType string
	UID        string
	ATQA       string
	SAK        string
	MifareType string
	Blocks     map[int]string
	Headers    map[string]string
}

NFCFile is a parsed Flipper NFC capture. Block contents stay as the raw space-separated hex strings so a round-trip preserves exactly what came off the wire; block numbers are their integer position for easy edits.

func ParseNFC

func ParseNFC(data []byte) (*NFCFile, error)

ParseNFC parses a Flipper .nfc capture file. Accepts CRLF, LF, missing final newline, # comments, and blank lines. Block lines ("Block 0: ...") become entries in Blocks; unknown headers fall through to Headers so round-tripping is lossless.

func (*NFCFile) Marshal

func (n *NFCFile) Marshal() []byte

Marshal serializes n back to canonical .nfc bytes. Core headers come first (Filetype → Version → Device type → UID → ATQA → SAK → MifareType), then unknown headers in sorted order, then Block lines in ascending numeric order.

type NRF24Target added in v0.3.1

type NRF24Target struct {
	// Address is the 5-byte NRF24 pipe address, uppercase
	// colon-separated (e.g. "A1:B2:C3:D4:E5"). The Mouse Jacker FAP
	// matches bytes verbatim — whitespace or lowercase confuses it.
	Address string

	// Rate is the NRF24 data rate the sniffer observed the address at.
	// 1 = 1 Mbps (most Microsoft peripherals), 2 = 2 Mbps (Logitech
	// Unifying / MX family). A '250' value means 250 kbps, rare on
	// modern peripherals.
	Rate int
}

NRF24Target is one captured wireless-peripheral address.

func ParseNRF24Addresses added in v0.3.1

func ParseNRF24Addresses(src string) ([]NRF24Target, []string, error)

ParseNRF24Addresses parses the addresses.txt shape the NRF24 Sniffer FAP writes. Malformed lines are skipped with a non-fatal error aggregated in the returned slice — callers log the count and continue. Returns an error only when the whole file is empty / unparseable.

type RFIDBuildParams added in v0.3.0

type RFIDBuildParams struct {
	// KeyType is the LF protocol name: EM4100, HIDProx, Indala,
	// AWID, FDX-A, FDX-B, etc. Accepted verbatim — the caller is
	// responsible for matching the protocol to the data.
	KeyType string

	// Data is the hex payload, e.g. "1A 2B 3C 4D 5E". Spaces
	// between octets are tolerated; non-hex input is rejected.
	Data string
}

RFIDBuildParams carries inputs for BuildRFID. Both KeyType and Data are required.

type RFIDFile

type RFIDFile struct {
	Filetype string
	Version  int
	KeyType  string
	Data     string
	Headers  map[string]string
}

RFIDFile is a parsed Flipper .rfid (125 kHz LF) capture.

func ParseRFID

func ParseRFID(data []byte) (*RFIDFile, error)

ParseRFID parses a Flipper .rfid file. Accepts CRLF, LF, a missing final newline, and # comments.

func (*RFIDFile) Marshal

func (r *RFIDFile) Marshal() []byte

Marshal serializes r back to canonical .rfid bytes.

type SubBruteforceParams added in v0.3.0

type SubBruteforceParams struct {
	Frequency uint32 // Hz
	BitCount  int    // typically 24 for Princeton-family protocols
	StartKey  uint64 // inclusive
	EndKey    uint64 // inclusive
	TE        int    // microseconds (defaults to 400 — common Princeton TE)
	Preset    string // optional; defaulted per-band via defaultSubPreset
}

SubBruteforceParams carries the inputs for BuildSubBruteforce. Produces a RAW .sub file encoding Princeton-style OOK pulses for each key in [StartKey, EndKey]. Frequency + bit count + TE are required; the sweep is capped by maxBruteforceKeys to prevent the caller from generating a megabyte-sized file and thrashing the Flipper SD card.

type SubBuildParams added in v0.3.0

type SubBuildParams struct {
	// Frequency in Hz (e.g. 433920000 for 433.92 MHz). Rejected if
	// zero or outside the 1 MHz–1 GHz range the Flipper CC1101 can
	// reach.
	Frequency uint32

	// Protocol name, e.g. "Princeton", "Keeloq", "RAW". Optional —
	// omitted files default to the RAW / unrecognised path.
	Protocol string

	// Preset name understood by the Flipper firmware. Leave empty to
	// have BuildSub pick a default from the frequency band.
	Preset string

	// Key is a space-separated hex byte string, e.g.
	// "1A 2B 3C 4D 00 00 00 00". Produces the Key: line.
	Key string

	// Bit is the protocol's bit-length (e.g. 24 for Princeton,
	// 32 for Came).
	Bit int

	// TE is the protocol's timing element in microseconds. Defaults
	// to 400 (a common Princeton TE) when zero and Protocol is set.
	TE int

	// RawData produces a RAW file instead of a keyed one. When set,
	// Protocol is overridden to "RAW".
	RawData []int32
}

SubBuildParams carries the inputs for BuildSub. Frequency is the only required field; everything else is optional and falls back to sensible defaults (Preset = Ook650Async which matches the majority of ISM-band captures).

type SubFile

type SubFile struct {
	Filetype  string
	Version   int
	Frequency uint32
	Preset    string
	Protocol  string
	Bit       int
	Key       string
	TE        int
	RawData   []int32
	Headers   map[string]string
}

SubFile is a parsed Flipper Sub-GHz capture file. Both the structured key-file layout and the RAW capture layout share this struct; RawData is populated only when the file is a RAW capture (Filetype contains "RAW").

Headers preserves any key:value lines the parser did not promote into a strongly-typed field — keeps round-tripping lossless for firmware-fork extensions (e.g. Momentum's "Bit Raw Protocol" additions) we don't model.

func ParseSub

func ParseSub(data []byte) (*SubFile, error)

ParseSub parses a Flipper .sub capture. Accepts CRLF, LF, trailing or missing final newline, and ignores # comments + blank lines. Unknown key:value lines are preserved in Headers so marshal round-trips.

func (*SubFile) Marshal

func (s *SubFile) Marshal() []byte

Marshal serializes s back to canonical .sub bytes. Emission order: Filetype, Version, Frequency, Preset, Protocol, <headers>, Bit, Key, TE, then RAW_Data lines chunked at rawChunkSize samples per line to match the Flipper firmware's own output pattern.

type SubFreqSweepParams added in v0.3.1

type SubFreqSweepParams struct {
	Frequencies []uint32 // Hz, one file produced per entry
	BitCount    int
	StartKey    uint64
	EndKey      uint64
	TE          int
	Preset      string
}

SubFreqSweepParams carries the inputs for BuildSubBruteforceSweep. Produces one RAW .sub byte stream per frequency in the list, each sweeping the same [StartKey, EndKey] range at BitCount bits. The caller pairs the returned byte streams with per-frequency filenames and writes them separately.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL