// Package capturecmd is the one-shot phone MITM capture command, exposed as a // library so the single drm binary can host it as a subcommand. package capturecmd import ( "flag" "fmt" "strings" "time" "drmdecryption/adb" "drmdecryption/app" "drmdecryption/modcfg" "drmdecryption/phonecap" ) // Run performs one capture. App modules are compiled in, so there is no modules // directory to scan: --app names a registered provider. func Run(args []string) error { fs := flag.NewFlagSet("capture", flag.ExitOnError) appName := fs.String("app", "", "app module. Empty = passive MITM, no launch/auto-play") channel := fs.String("channel", "", "channel id for --auto-play") autoPlay := fs.Bool("auto-play", false, "auto-navigate to --channel (requires --app and --channel)") waitSec := fs.Int("wait", 180, "seconds to wait for license/mpd capture") python := fs.String("python", "", "python for wvkey.py (default: .venv)") wvd := fs.String("wvd", "", "path to .wvd device file (required for key fetch)") userAgent := fs.String("user-agent", "", "optional User-Agent for license requests") serial := fs.String("serial", "", "adb device serial (default: sole/USB selected device)") closeApp := fs.Bool("close", true, "force-stop --app when capture finishes (ignored in passive mode)") keyMode := fs.String("key-mode", "", "override the app's key mode: modulardrm | raw | none") // Each module contributes --. overrides for its local values. app.BindFlags(fs) fs.Usage = func() { fmt.Fprintf(fs.Output(), "capture — one-shot phone MITM capture\n\nUsage:\n capture --app <%s> [--channel ID] [--auto-play] [--wvd PATH]\n capture (passive: play anything on the phone)\n\nFlags:\n", strings.Join(app.Names(), "|")) fs.PrintDefaults() } if err := fs.Parse(args); err != nil { return err } c := adb.New() if *serial != "" { c = c.WithSerial(*serial) } // No --app: passive MITM — the operator plays whatever they want and we dump // whatever DRM traffic appears. if strings.TrimSpace(*appName) == "" { _, err := phonecap.RunPassive(phonecap.Options{ Client: c, Python: *python, WVD: *wvd, UserAgent: *userAgent, Wait: time.Duration(*waitSec) * time.Second, KeyMode: *keyMode, ClearProxy: true, }) return err } a, err := app.Get(*appName) if err != nil { return err } if *autoPlay && strings.TrimSpace(*channel) == "" { return fmt.Errorf("--auto-play requires --channel (%s)", knownChannels(a)) } if *channel != "" && !a.HasChannel(*channel) { return fmt.Errorf("unknown channel %q for %s (%s)", *channel, a.Name(), knownChannels(a)) } _, err = phonecap.Run(phonecap.Options{ App: a, Channel: *channel, Client: c, Python: *python, WVD: *wvd, UserAgent: *userAgent, Wait: time.Duration(*waitSec) * time.Second, NoAutoPlay: !*autoPlay, CloseApp: *closeApp, KeyMode: *keyMode, ClearProxy: true, }) return err } // knownChannels describes what the operator can pick, or where to declare it when // the module has no local values file yet. func knownChannels(a app.App) string { ids := []string{} for _, ch := range a.Channels() { ids = append(ids, ch.ID) } if len(ids) == 0 { return "no channels configured — add " + modcfg.Path(a.Name()) } return "known: " + strings.Join(ids, ", ") }