Platform index / Video presentation switcher
Analog Way Midra series
Six models, one firmware, one set of bitstreams. The layer engine lives in FPGA fabric that is configured identically on every unit in the range, so the tier a machine sells as is decided by an integer it reads at boot.
The load-bearing finding. The updater flashes one CAMELYA.hex, one FELINDRA.hex, one TATIANA.hex and one MCU application to every model in the family, with no per-model branching anywhere in the script. The compositor is in those bitstreams — so a Pulse2 is running the identical silicon configuration to an Eikos2.
Verdict. Within a platform the model tier is enforced in software, from a device-type integer read at boot; between the base and -H boards it is genuinely different hardware. The evidence for the first half is the update script, which has no per-model branching and ships exactly one bitstream per FPGA for the whole family, plus a single MCU application that carries the complete FE1–FE6 chip map and the complete sixteen-model table. The evidence for the second half is in the same package: the Chtiderman build replaces the analog RGBHV output with Valens VS2000 HDBaseT transmitters and receivers, which is a different board, not a different setting. What is really built differently between the base models is the connector and daughter-card population — the two optional SDI cards, and which of the six front ends and their EDID EEPROMs are fitted. A Pulse2 does not have fewer layers because it has less silicon. The control plane recovered since is consistent with all of it: one variable table for the whole family, dimensions declared per model, and a device that hands you its own device-type integer the moment you connect.
Method
Two bases, deliberately kept apart. The silicon findings are static analysis of one vendor updater package, cross-read against the vendor's own control client — no unit was opened, and the package's own manufacturer update log from a real PLS350 is their observation of a live machine, not ours. The control-plane findings are not file-only: the client's AVM2 bytecode was decompiled and its variable-table constructor replayed to reconstruct the whole control model, and on 2026-08-06 a real Pulse2 on our LAN was read and written through our own client, with RCS2's own session against it captured through a logging proxy.
Artifacts examined
midra_updater_v02_02_02.exe— Inno Setup 5.5 installer containingDevice.xml,Script.xml, six module scripts and every firmware component as plaintext Intel HEXLogs.txt, shipped inside that package — a 2013 update session against a PLS350, naming the components a physical unit answered forMDR_launcher.swf— the RCS2 control client; its AVM2 constant pool carries the completeDEV_*,CARD_*,LAYER_*and plug enumerationsRCS2.app— the Midra client, an Adobe AIR application (MDR.RCSv2.2.03). TheMDR_launcher.swfinside it is aCWS(zlib) container whoseVars::VarCominitialiser carries the entire control model: 562 variables in 49 groups, each constructed with 15–16 positional arguments — name, group, min, max, default, dimension list, request and answer mnemonics, and flags- A logging proxy between RCS2 and a live Pulse2, recording the exact command sequence the vendor client sends to assign a layer source — the one operation the recovered table alone does not explain
No manufacturer firmware, binaries or documentation are redistributed here. Artifacts are referenced by name and version only. See the method and legal statement for the basis on which this analysis was performed and the boundaries it observes.
Hardware architecture
| Item | Finding | Confidence |
|---|---|---|
| Host MCU | One micro, codenamed CALIMERO. Dual-bank flash at 0x1A000000 and 0x1B000000 with the application 32 kB into bank A, 512 kB per bank, and an SRAM stub executed at 0x20000000 — that is the NXP LPC18xx / LPC43xx memory map. Product.xml corroborates with an NXPBootStartTimeout parameter. The exact part was not identified | Inferred |
| Flash layout | Application code 0x1A008000–0x1A07D7CF (481,232 B); const data 0x1B000000–0x1B02EB0B (191,244 B); factory-test data 0x1B030000; factory-test code 0x1B040000. Every string, chip name and lookup table lives in the const image, not the code image | Verified |
| FPGAs | Three, codenamed CAMELYA, FELINDRA and TATIANA, each addressed as its own daughter card (CARD_CF_CAMELYA, CARD_CF_FELINDRA, CARD_CF_TATIANA). One bitstream file each | Verified |
| Bitstream container | Intel HEX wrapping a 43-byte proprietary header at 0x20 and a payload from 0x33F. Payloads are 3,222,997 / 3,348,122 / 3,279,834 bytes at 6.50 / 6.61 / 6.58 bits per byte — uncompressed structured data, i.e. raw configuration bitstreams | Verified |
| FPGA vendor | Not identified. No Xilinx sync word, .bit header or Altera/Lattice preamble survives any bit rotation, byte reversal or inversion of the payload. Three different lengths for uncompressed bitstreams implies three different parts rather than one part carrying three designs | Unknown |
| Input silicon | FE1–FE4 Analog Devices ADV7842 — HDMI receiver, analog AFE, colour processor, SD decoder, InfoFrame and HDCP repeater, each a separate I²C sub-address. FE5–FE6 ADV7611, HDMI only. Read from the firmware's own I²C device-name table | Verified |
| Output silicon | BE1 and BE2 each carry a Silicon Image SiI9024 HDMI transmitter, a TI THS8200 video DAC and a Silicon Labs Si570 programmable pixel clock. BE3 is an ADV7301 or ADV7341 for the monitoring output | Verified |
| Audio | Cirrus Logic CS8422 × 2 (asynchronous sample-rate converter / AES receiver) and CS4265 × 2 (codec) | Verified |
| EDID | One EEPROM per connector, not per input — 18 individually addressable positions spanning EDID_ANA1..4, EDID_DVI1..4, EDID_HDMI1..6 and EDID_HDBT1..4. The 2013 PLS350 log shows a real unit answering for twelve of them | Verified |
| Daughter cards | CARD_MOTHERBOARD, the three FPGA cards, CARD_CF_AUDIO, CARD_CF_FAV, CARD_CF_VIDEO_OUT, CARD_CF_SDI_IN_1, CARD_CF_SDI_IN_2, CARD_XPORT. SDI input is two optional cards — "CF" is carte fille | Verified |
Software architecture
| Item | Finding | Confidence |
|---|---|---|
| Operating system | No FreeRTOS, ThreadX, µC/OS, embOS or Linux string appears in either image, and the client's microcontroller enum has exactly one entry (MIC_CALIMERO). A single bare-metal micro drives the whole machine | Verified |
| Model table | A flat 16-entry, 12-byte-stride string table at 0x1B02808C: MDRdbg, six base models, MNMdbg, six -H models and two -H LE models. Device type is 256 + i across the first block and 280 + j across the second | Verified |
| Models the updater accepts | Twelve, in Device.xml: 257 EKS550 (Eikos2), 258 SPX450 (Saphyr), 259 PLS350 (Pulse2), 260 SMX250 (SmartMatriX2), 261 QMX150 (QuickMatriX), 262 QVU150 (QuickVu), then 282–285 and 287–288 for the -H and -H LE variants | Verified |
| Models the firmware knows | Sixteen. 281 EKS550-H and 286 QVU150-H exist in the firmware model table and in the control client's DEV_* list, but are absent from the updater's compatibility list — built into the code and, apparently, never shipped | Verified |
| Two platforms, one package | The updater carries two complete MCU applications — Calimero_* ("MDR") and Chtiderman_* ("MNM") — and flashes both unconditionally. Only the Chtiderman model table contains the two LE SKUs | Verified |
| Layer model | Per screen: LAYER_FRAME background, LAYER_PIP_1..4, LAYER_LOGO_1..2 and LAYER_AUDIO. Two screens (SCREEN_S1, SCREEN_S2), two outputs, main and preview. Keying is luma or chroma, each with a downstream variant | Verified |
| Protocol space | The superset, not any one model: 10 inputs across 5 plug types (ANALOG_HD15, DVI, SDI, HDMI, HDBASET), 2 outputs across 8 plug types, 8 still frames and 8 logos, 8 preset memory slots, 3 case fans | Verified |
| Control interfaces | RS-232 at 115200 8N1, USB CDC, and TCP 10500. Product.xml gives all three, with a factory default of 192.168.2.140 | Verified |
| Control protocol | A terse ASCII protocol on TCP 10500, and it is asymmetric: an outbound command puts the mnemonic last (1,2,5PMinp sets, 1,2,PMinp requests) while the device's reply puts it first (PMinp1,2,5), with the final comma-separated field as the value. Midra's client appends CRLF itself; the device answers CRLF. Mnemonics are five characters, with three debug specials whose request and answer differ — ?→DEV, @→ADBG, >→DDBG | Measured |
| The variable table | 562 variables in 49 groups, 190 of them read-only status. Every one declares its own dimension list, minimum, maximum and default — but those are the vendor's declarations, read out of the client, not limits anyone has measured. Treat them as strong hints; the device is the authority | Verified |
| Device identity on the wire | Midra has no PDEV — asking for it draws E10. It pushes DEV on connect (259 on our Pulse2) and answers DIdsn serial (2165), VEvar version (13) and DIdre reference (240215). A Midra model map keys on DEV; the LiveCore generation's keys on PDEV, and a client that assumes one shape sees the other generation as a dead socket | Measured |
| Screen and layer model, as a Pulse2 reports it | Up to 2 screens; SCmly maximum layers is read-only 0–4 and reads 2 for screen 0 on this unit; layers are indexed [screen(2), ctx(3), layer(8)]; INava[10] available inputs reads 8. Geometry carries the same +32768 centre bias as LiveCore — a centred 1080p layer reads PRpoh=33728, PRpov=33308, PRsih=1920, PRsiv=1080 — so the two generations share their positioning arithmetic even where they share no mnemonics | Measured |
| Take and layout verbs | GCtak[screen] takes one screen, GCtal takes all, GCtba is the T-bar, with GCtav/GCtio as status. GCqly[screen,ctx] applies one of 27 built-in layouts and reads back 0, because it is a momentary trigger rather than stored state, and it sets geometry only — sources are assigned separately. There is no selected-layer variable at all, which is a Midra/LiveCore difference | Measured |
| Programme is protected; preview is not | Geometry writes are accepted, echoed back and raise GCtav (TAKE_AVAILABLE): the device holds the edit as a pending change until a take commits it. A source write aimed at the programme context is echoed back as the old value with a CTsto pulse — the device declines it. The vendor client's own path, captured through the proxy, writes the source to the preview context with preset-update-mode on; a programme-context edit re-baselined with PIwur sticks too. Both were confirmed on the unit | Measured |
| Memory model | Eight preset slots. PMpst[slot] is the used flag and the stored content is readable per slot but not writable — a direct write to stored content reverts the same way a protected live write does. Save and erase are request variables. Recall is not a device command at all: the vendor client reads the stored slot and re-applies it to the live preset, so on this generation a memory recall is client-side arithmetic, and any controller that expects a one-command recall is waiting for a reply that never comes | Measured |
| HTTP surface | The device serves HTTP on port 80 alongside the control socket, including per-input thumbnails as BMPs cache-busted by timestamp (/assets/Snapshots/capture_in_8.bmp?time=…). Source previews in a third-party controller cost nothing to build | Measured |
Update path & security model
| Item | Finding | Confidence |
|---|---|---|
| Image signing | None. No RSA, SHA, AES, HMAC or ECDSA string appears in either image, and every firmware component ships as plaintext Intel HEX — no container, no compression, no obfuscation | Verified |
| Update validation | A version check, a transfer and a read-back verify per component (Kd, Th, Kf in the script grammar). There is no signature step in the grammar at all | Verified |
| Bootloader | Two-stage. A resident bootloader at node address 1 exposes only MICRO_RAM; the updater pushes a 40,900-byte stub into AHB SRAM, jumps to it with the script's Ga 0x20000000 opcode, and that stub performs every flash operation | Verified |
| Entitlement | None found. No licence, unlock, activation, option-key or dongle string in either MCU image or in the control client. This generation has no feature-key system — unlike the LiveCore generation that followed it, which has one | Verified |
| Control-plane authentication | None observed. The control port accepts a session with no credential step and starts pushing state immediately — the Pulse2 pushes its own DEV device code on connect — and the socket that reads is the socket that writes. Concurrency is the unmeasured half: Analog Way ship AWGateway, an application whose entire purpose is to hold one session to the device and re-serve port 10500 to several clients, which is what you build when a device accepts very few. The limit itself was never measured | Measured |
| Rejected commands | A rejected command is answered E | Measured |
These rows describe how a platform validates a firmware image, because that is a structural fact about its architecture. They are not a vulnerability disclosure and no exploit, bypass or circumvention technique is published here. Licensing and entitlement mechanisms are out of scope throughout — seescope boundaries.
What is programmable
- 1The control protocol
ASCII over TCP 10500 or RS-232, and no longer merely recoverable: the full 562-variable table has been reconstructed from the vendor's own client and the framing, identity, geometry, take and memory paths confirmed on a real Pulse2. The client is an Adobe AIR application and the platform is long out of support, so a modern replacement controller is the obvious and entirely legitimate project — and it needs nothing from the firmware.
- 2EDID management
One EEPROM per connector, individually addressable through the documented update path, with the stock EDIDs shipped as readable 256-byte HEX. This is the one part of the machine's behaviour a third party can change through a supported mechanism.
- 3The FPGA fabric — in principle only
The compositor is entirely in the three bitstreams, so nothing about the video path can be changed without them. They are unencrypted, but the container is proprietary and the parts are unidentified, which puts any real work here behind two unsolved problems and a bench.
Traps
Mistakes this analysis actually made, or came close to making. They are recorded because each one produces a plausible-looking wrong answer rather than an obvious failure.
- The first 80 bytes of every bitstream payload contain only
0x00and0x80, which is exactly what a bitstream expanded to one configuration bit per byte for GPIO bit-banging looks like. Decoding on that assumption produced0f f0 0f f0— very close to the Xilinx.bitmagic — and a confident wrong answer. The byte histogram of the whole payload killed it:0x44,0x22,0x81and0x88are all common further in. A structured header region can imitate an encoding for as long as you only look at the header. Midra_Simulator.appis not the Midra series. Its EDIDs areMDR4K_*andALTA4K_*— it belongs to the later, unrelated Midra 4K / Alta 4K line, and it is an HTML5 application where this generation's client is Flash. Analysing it answers a different question entirely.- Reading "one firmware package" as "one machine". The single-image evidence proves the software is common across the family; the very same package also carries two entirely different MCU applications for two different I/O boards. Stopping at the first conclusion would have missed the
-Hplatform completely. - The vendor's own update log inside the package is the most valuable file in it and looks like debris. It is the only record here of a physical unit's responses — which device type it reported, and which of the eighteen EDID positions actually existed.
- Reading the first reverting writes as a locked device. A source write to a layer comes back as the old value, which looked like the device refusing remote source assignment outright — and this entry nearly said so. It was two separate things: the programme context is protected while the preview context is not, and the device declines to place an input carrying no signal on a layer, so with nothing patched in every real input reverts while an always-available internal source sticks. The mechanism was right the whole time; the test was wrong twice over.
- Putting a comma before the mnemonic on a set.
1,2,5PMinpsets the variable;1,2,5,PMinpis read as a get of an over-indexed variable and is silently ignored or answeredE13. The value is glued to the mnemonic with no separator, which reads like a typo and is the protocol.
Open questions
- Which FPGAs these actually are. The bitstreams are raw and unencrypted but sit in an Analog Way container with no vendor magic, and the three differing lengths point at three different parts. A board photograph or a JTAG IDCODE settles it in seconds and nothing in the files will.
- External frame memory. One bitstream can address different DDR depths, so it remains possible that the higher tiers are stuffed with more frame RAM and the firmware limit merely tracks what is fitted. This is the single caveat on the software-tiering conclusion and it cannot be closed from files.
- Where the device-type integer is stored. The update log shows the unit reporting
Detected Device Type [259]alongside a device ID, but the storage location was not located and was not looked for. - What the 43-byte bitstream header encodes. It is identical for the first eight bytes across all three FPGAs and diverges after, with the last two bytes differing — consistent with an identifier plus a checksum, but not decoded.
- The exact on-air semantics. A take commits a pending change, but the per-layer status variables read as small stable enums that did not flip under one, so what is actually on the programme output cannot be settled from the wire alone. It needs a monitor on the output while the socket is watched.
- How many concurrent control sessions the device accepts.
AWGateway's existence implies very few, possibly one. It was never measured, and it decides whether a third-party controller can coexist with the vendor client at all. - Whether a set outside a variable's declared range is clamped, rejected or accepted. Every range on this page is the client's declaration, and a declaration is not a limit.
- The rest of the preset-memory group. Thirty-one variables are enumerated and only the used flag, the per-slot content and the save and erase requests have been exercised on hardware.
Status of this entry. A written teardown exists and its claims carry evidence citations. Rows labelled Measured were read off a running unit; everything else on this page is derived from files and has not been checked against hardware.