Claude's Explanation:
MS2131 60Hz Firmware Patch Guide
Overview
The MacroSilicon MS2131 USB HDMI capture card (Monster MAD1-2024-BLK) ships with firmware that limits 1080p MJPEG capture to 50fps over USB 2.0. The chip supports 60fps — both 50fps and 60fps frame intervals exist in the USB 3.0 descriptors — but the USB 2.0 descriptors only advertise 50fps.
This guide documents the process of patching the firmware to enable 60fps over USB 2.0, specifically for use with LinkPi ENC1-V3 encoders which only have USB 2.0 ports.
Prerequisites
- Windows PC with Python 3.x installed
-
ms213x_flash.exe from steve-m/ms213x_flash
- Monster MAD1-2024-BLK capture card (MS2131 chipset, VID
345f, PID 2131)
- USB cable to connect the card to the PC
Tools
| Tool |
Purpose |
Source |
ms213x_flash.exe |
Backup and flash firmware |
GitHub Releases |
patch_final.py |
Patch 50fps→60fps and fix checksum |
Custom (below) |
Step 1: Backup Original Firmware
Connect the Monster card to the PC via USB. Run:
ms213x_flash.exe
This auto-detects the MS2131 and saves a backup file like:
MS2131_2023-06-10_backup_25f7_62d6.bin
Keep this backup safe. You can always restore it to revert the patch.
Step 2: Patch the Firmware
Save the following script as patch_final.py:
import shutil
import sys
# --- Configuration ---
# Path to your original firmware backup
src = r'C:\Users\David\Desktop\MS2131_2023-06-10_backup_25f7_62d6.bin'
# Path for the patched output
dst = r'C:\Users\David\Desktop\MS2131_60hz_final.bin'
# --- Copy original to new file ---
shutil.copy2(src, dst)
data = bytearray(open(dst, 'rb').read())
# --- Read firmware structure ---
# Header length is 0x30 (48 bytes)
HEADER_LEN = 0x30
# Code length is stored at offset 0x02-0x03 (big-endian)
code_len = (data[0x02] << 8) | data[0x03]
print(f'Code length: {code_len} (0x{code_len:04x})')
print(f'Header: {HEADER_LEN} (0x{HEADER_LEN:02x})')
print(f'Checksum offset: {HEADER_LEN + code_len} (0x{HEADER_LEN + code_len:04x})')
# --- Verify original checksums ---
orig_header_csum = (data[HEADER_LEN + code_len + 0] << 8) | data[HEADER_LEN + code_len + 1]
orig_code_csum = (data[HEADER_LEN + code_len + 2] << 8) | data[HEADER_LEN + code_len + 3]
print(f'Stored header checksum: {orig_header_csum:04x}')
print(f'Stored code checksum: {orig_code_csum:04x}')
# Verify we can reproduce the original code checksum
calc = 0
for i in range(HEADER_LEN, HEADER_LEN + code_len):
calc += data[i]
calc = calc & 0xFFFF
print(f'Calculated code checksum (before patch): {calc:04x}')
if calc != orig_code_csum:
print('ERROR: Cannot reproduce original checksum. Aborting.')
sys.exit(1)
print('Original checksum verified OK')
# --- Patch 50fps -> 60fps ---
# UVC frame intervals are stored in 100-nanosecond units (little-endian):
# 50fps = 200000 = 0x00030D40 -> bytes: 40 0D 03 00
# 60fps = 166666 = 0x00028B0A -> bytes: 0A 8B 02 00
old = b'\x40\x0D\x03\x00'
new = b'\x0A\x8B\x02\x00'
count = 0
i = 0
while i < len(data) - 3:
if data[i:i+4] == old:
data[i:i+4] = new
count += 1
i += 1
print(f'\nPatched {count} occurrences of 50fps -> 60fps')
# --- Recalculate code checksum ---
# The code checksum is a simple 16-bit sum of all bytes in the code section
# (from HEADER_LEN to HEADER_LEN + code_len)
new_csum = 0
for i in range(HEADER_LEN, HEADER_LEN + code_len):
new_csum += data[i]
new_csum = new_csum & 0xFFFF
print(f'New code checksum: {new_csum:04x}')
# --- Write new checksum ---
# Checksum is stored big-endian at offset HEADER_LEN + code_len + 2
data[HEADER_LEN + code_len + 2] = (new_csum >> 8) & 0xFF
data[HEADER_LEN + code_len + 3] = new_csum & 0xFF
# --- Verify ---
verify = (data[HEADER_LEN + code_len + 2] << 8) | data[HEADER_LEN + code_len + 3]
print(f'Written code checksum: {verify:04x}')
# --- Save ---
open(dst, 'wb').write(data)
print(f'\nSaved to {dst}')
print(f'Original backup untouched: {src}')
Run it:
python patch_final.py
Expected output:
Code length: 64416 (0xfba0)
Header: 48 (0x30)
Checksum offset: 64464 (0xfbd0)
Stored header checksum: 25f7
Stored code checksum: 62d6
Calculated code checksum (before patch): 62d6
Original checksum verified OK
Patched 37 occurrences of 50fps -> 60fps
New code checksum: 6d19
Written code checksum: 6d19
Saved to C:\Users\David\Desktop\MS2131_60hz_final.bin
Original backup untouched: C:\Users\David\Desktop\MS2131_2023-06-10_backup_25f7_62d6.bin
Step 3: Flash the Patched Firmware
ms213x_flash.exe -w MS2131_60hz_final.bin
The tool will:
- Back up the current firmware automatically
- Erase flash
- Write the patched firmware
- Verify the flash contents
Wait for "Successfully written and verified flash" before unplugging.
Step 4: Verify on LinkPi
- Unplug the Monster card from the PC
- Plug it into the LinkPi USB port
- Open the LinkPi web UI
- The USB channel should now show 60Hz as the framerate option
If the LinkPi config still says 50, update it:
ssh root@<linkpi-ip>
sed -i 's/"framerate": 50/"framerate": 60/' /link/config/config.json
reboot
Restoring Original Firmware
If anything goes wrong, reflash the original backup:
ms213x_flash.exe -w MS2131_2023-06-10_backup_25f7_62d6.bin
How the Patch Works
UVC Frame Intervals
USB Video Class (UVC) devices advertise supported framerates as "frame intervals" in 100-nanosecond units:
| Framerate |
Interval (decimal) |
Interval (hex, little-endian) |
| 60 fps |
166,666 |
0A 8B 02 00 |
| 50 fps |
200,000 |
40 0D 03 00 |
| 30 fps |
333,333 |
15 16 05 00 |
| 25 fps |
400,000 |
80 1A 06 00 |
| 20 fps |
500,000 |
20 A1 07 00 |
| 10 fps |
1,000,000 |
40 42 0F 00 |
The patch replaces all occurrences of the 50fps interval (40 0D 03 00) with the 60fps interval (0A 8B 02 00) in the firmware binary.
Firmware Checksum
The MS2131 firmware has two checksums stored at the end of the code section:
-
Header checksum (2 bytes): Sum of header bytes (not modified by this patch)
-
Code checksum (2 bytes): 16-bit sum of all bytes in the code section
The checksum calculation (from ms213x_flash.c):
uint16_t calculate_code_checksum(uint8_t *data, int len) {
uint32_t csum = 0;
for (int i = 0; i < len; i++)
csum += data[i];
return csum & 0xffff;
}
After patching the frame intervals, the code checksum must be recalculated and written back, otherwise ms213x_flash will refuse to flash with "Code checksum mismatch."
Firmware Layout
Offset 0x0000 - 0x002F: Header (48 bytes)
0x00-0x01: Magic bytes (0x3C 0xC3)
0x02-0x03: Code length (big-endian)
Offset 0x0030 - 0xFBCF: Code section (64,416 bytes)
Contains UVC descriptors, MJPEG encoder config, USB stack, etc.
Offset 0xFBD0 - 0xFBD3: Checksums (4 bytes)
0xFBD0-0xFBD1: Header checksum (big-endian)
0xFBD2-0xFBD3: Code checksum (big-endian)
Offset 0xF830 - 0xF92F: EDID (256 bytes, in user data area)
Bandwidth Considerations
1080p60 MJPEG over USB 2.0:
-
Typical bitrate: ~96 Mbps (200KB/frame × 60fps)
-
Worst case: ~144 Mbps (300KB/frame × 60fps)
-
USB 2.0 isochronous throughput: ~280-350 Mbps practical
The 60fps MJPEG stream uses roughly 30-40% of available USB 2.0 bandwidth, well within limits.
Files
| File |
Description |
MS2131_2023-06-10_backup_25f7_62d6.bin |
Original unmodified firmware backup |
MS2131_60hz_final.bin |
Patched firmware with 60fps USB 2.0 support |
patch_final.py |
Patch script |
ms213x_flash.exe |
Flash tool (from GitHub) |
Hardware
-
Capture Card: Monster MAD1-2024-BLK
-
Chipset: MacroSilicon MS2131 (VID
345f, PID 2131)
-
Firmware Date: 2023-06-10
-
Encoder: LinkPi ENC1-V3 (SS524V100, USB 2.0 only)
References
I also want to clarify that I used a slightly different version of the script in production because I had to figure out an issue with the checksum, so I had to patch it a couple of times before it finally worked. This is Claude's amalgamation of everything we did. So obviously proceed at your own risk, although I will be happy to provide a firmware file to anyone who wants to try this on one of these monster capture cards from Walmart.
Edit: I confirmed that this script produces an identical file to what I flashed on my cards. I took the original that used an earlier iteration of it and recreated the .bin file and the checksums were the same. Regardless I have the firmware file saved and will send it to whoever would like a copy.
Edit1: I thought I should update this and just say I was experiencing weird picture quality issues after flashing this. So I went back to stock and returned the cards and just ended up buying another encoder.
Although for USB 2.0 50 Hz, they're solid.