← All journal entries

Cracking X32-Edit's Connection Problem

X32-Edit refuses to connect without UDP broadcast discovery. The fix: query the mixer directly, then spoof the discovery response locally so X32-Edit thinks it found the mixer on its own.

The Idea

I’ve been building a web-based equipment management platform (AV Server) for our broadcast operations. We already had one-click launchers working for Blackmagic gear — ATEM switchers, Videohub routers, Ultimatte keyers. You click a device on the equipment list, and the correct software opens pre-connected to that device’s IP. No hunting for IPs, no manual setup.

The Behringer X32 mixers were the missing piece. We have a bunch of them across our studios and I wanted the same experience — click a mixer on the site, X32-Edit opens and connects. Should be straightforward, right?

It was not straightforward.

How the Other Launchers Work

For context, the Blackmagic apps were pretty simple. They all store their connection settings in predictable places — Windows registry keys or macOS defaults plist entries. The flow is:

  1. User clicks device on AV Site
  2. Browser fires a custom protocol URL like avserver-atem://192.168.81.50
  3. Our launcher script catches it, writes the IP to the app’s config, kills any running instance, relaunches

Clean and reliable. So I figured X32-Edit would be similar — find where it stores its connection IP, write to it, relaunch.

First Approach: The Prefs File

X32-Edit stores settings in a prefs file — %APPDATA%\X32-Edit\X32Edit.prefs on Windows, ~/Library/.X32-Edit/X32Edit.prefs on Mac. Inside there’s an IP address field and autoconnect setting. The plan was to rewrite these before launching.

I actually liked this approach because it worked across versions. Older versions of X32-Edit (pre-4.3) don’t have autoconnect, but they’d still open with the correct IP pre-filled in the Setup dialog — the user just clicks Connect and selects “Mixer to PC.” You’d have to do that step anyway when connecting to an X32 through X32-Edit, so it wasn’t a bad experience. And newer versions (4.3+) with the -i flag and autoconnect would just connect straight away. Best of both worlds.

bash
# v4.3+ supports direct IP flag
X32-Edit.exe -i 192.168.81.49 -x

Got it working on my machine, felt good about it. Shipped it off to test on a colleague’s PC.

Nothing. X32-Edit opened but didn’t connect. Tried everything — manipulating the prefs file, setting autoconnect, clearing cached connection data, even dealing with a .init file that X32-Edit uses to cache its last connection state. Nothing worked on his machine.

After a lot of back and forth I tried on a second colleague’s machine — same thing. Then I noticed: both of them were on wifi. My machine was on ethernet. When I switched mine to wifi — same problem.

The X32 mixers were still reachable over wifi. I could ping them. I could even connect manually by typing the IP into X32-Edit and clicking Connect. But neither the prefs approach nor the -i flag would trigger an automatic connection.

The Core Problem: UDP Broadcast Discovery

Here’s what I learned by digging into it: X32-Edit relies on UDP broadcast discovery on port 10023 for every automated connection method. When X32-Edit starts up, it broadcasts a discovery request across the local network. Mixers on the same subnet respond, and X32-Edit connects to them.

The -i flag? Still goes through broadcast discovery first. The autoconnect prefs setting? Same thing. If X32-Edit doesn’t get a broadcast response, it silently gives up. The IP you gave it doesn’t matter — it won’t attempt a direct connection.

On ethernet, our machines were on the same subnet as the X32s, so broadcast discovery worked fine. On wifi, different subnet — broadcasts don’t cross subnet boundaries. No discovery response, no connection.

This was the real problem. And since we’re deploying this across multiple locations where we don’t control the network setup (IT handles that), we can’t just ask them to fix broadcast routing. We needed a solution that works regardless of network topology.

The Breakthrough: Spoofing Discovery Locally

I was stuck on this for a while until I thought about it differently. X32-Edit insists on broadcast discovery? Fine — what if we give it exactly what it wants, but locally?

The key insight: while UDP broadcasts don’t cross subnets, direct UDP to a specific IP works perfectly fine. So I can query a mixer directly and get its discovery response:

x32_spoof.py python
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.sendto(b'/xinfo\x00\x00,\x00\x00\x00', ('192.168.81.49', 10023))
response, _ = udp.recvfrom(4096)

That response contains everything X32-Edit needs — the mixer’s IP, name, model, firmware version. Now the trick: bind to port 10023 on the local machine, wait for X32-Edit to send its broadcast, and replay the real mixer’s response as if the mixer was right there on the local network.

x32_spoof.py python
# Bind to the discovery port locally
fake = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
fake.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
fake.bind(('', 10023))

# Launch X32-Edit
subprocess.Popen(['/path/to/X32-Edit', '-i', TARGET_IP, '-x'])

# When X32-Edit broadcasts for discovery, replay the real response
end = time.time() + 5
while time.time() < end:
    try:
        data, addr = fake.recvfrom(4096)
        fake.sendto(response, addr)
    except socket.timeout:
        pass

X32-Edit sends its broadcast, our spoof catches it and responds with the real mixer’s info. X32-Edit thinks it discovered the mixer locally and connects directly. The mixer doesn’t know the difference.

Tested on ethernet — worked. Tested on wifi — worked. Different mixer IPs — all worked. This was the fix.

Making It Production-Ready

The concept was solid but turning it into a reliable zero-config launcher had its own challenges.

Controlling the version. Originally the prefs approach was nice because it worked across X32-Edit versions — old and new. But once we committed to the discovery spoof with the -i flag, we needed v4.3+ on every machine. Users might have older versions or might not have X32-Edit installed at all.

Solution: host the correct version on our Django server. I added new views and URL routes to serve the exe (Windows) and zipped app bundle (Mac) directly from our AV Server. The launcher auto-downloads the right version on first use — user clicks the button, the script grabs X32-Edit if needed, drops it in the right place, no manual installs.

views.py python
# Django view serving X32-Edit for Windows
@require_GET
def x32_windows(request):
    file_path = X32_DIR / "X32-Edit.exe"
    with open(file_path, "rb") as f:
        response = HttpResponse(f.read(), content_type="application/octet-stream")
    response["Content-Disposition"] = 'attachment; filename="X32-Edit.exe"'
    return response

Cross-platform UDP spoofing. Windows uses PowerShell for the UDP spoof (built into every Windows machine). Mac uses Python (built into every Mac). No extra dependencies for either platform.

On Windows, the VBS launcher writes a PowerShell script and runs it hidden — the user sees nothing except X32-Edit opening:

x32_launcher.vbs vbs
WshShell.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & psPath & """", 0, False

Failsafes. If the mixer is unreachable (network down, wrong IP, whatever), the spoof can’t get the discovery response. In that case the script falls back to just launching X32-Edit with the -i flag anyway — it won’t auto-connect, but at least the app opens. And if X32-Edit isn’t installed and the download fails, the script exits cleanly instead of erroring out.

macOS applet quirks. Our Mac launcher uses compiled AppleScripts as protocol handlers. After the first X32 launch, the applet process stayed running, and macOS wouldn’t fire a new open location event on subsequent clicks — so clicking a different mixer did nothing. The other protocol handlers (ATEM, Ultimatte) didn’t have this issue because their logic was fast and inline.

The fix was having the Python spoof script os.fork() to immediately return control to the AppleScript, then kill the applet process after the spoof completes. Next click launches a fresh applet instance and the event fires properly.

The Final Flow

Here’s what happens now when someone clicks an X32 mixer on AV Site:

  1. Browser fires avserver-x32://192.168.81.49
  2. Launcher catches the protocol URL
  3. Kills any running X32-Edit instance
  4. Queries the target mixer directly via UDP to get its real discovery info
  5. Binds to port 10023 locally
  6. Launches X32-Edit with -i flag
  7. X32-Edit broadcasts for discovery — our spoof responds with the real mixer’s info
  8. X32-Edit connects
  9. Spoof listener closes after 5 seconds

If X32-Edit isn’t installed, the launcher auto-downloads v4.3 from our server first. The whole thing is invisible to the user — they click a button and X32-Edit opens connected to their mixer. Works on ethernet, works on wifi, works across subnets.

What I Learned

The biggest takeaway: don’t assume software works the way its UI implies. X32-Edit has an IP address field, a connect button, and a -i command line flag. You’d think at least one of those would let you say “connect to this specific IP.” None of them do — they all funnel through broadcast discovery first.

Since we’re rolling this out across studios in different locations with different network configurations that we don’t control, we needed something that works universally. The discovery spoof approach handles that — as long as the mixer is reachable (even across subnets), we can get the connection info directly and feed it to X32-Edit locally.

Sometimes the cleanest solution to a software limitation is to just give it exactly what it expects. X32-Edit wants broadcast discovery? We’ll give it broadcast discovery — spoofed locally on the same machine. The whole thing took way longer than the initial “just write to a config file” estimate, but the result is solid and network-agnostic. One click, any mixer, any network.

← All journal entries