Статья объединяет практический разбор
.scr«руками» и полную техническую спецификацию формата.
.scrУпрощённая картинка файла .scr:
+------------------------+ 0x0000| Заголовок | (магия, версия, входное разрешение)+------------------------+ 0x0140±| Параметры сетки | (сколько строк и столбцов)+------------------------+ 0x014B±| Карта номеров карт | (какой номер в какой клетке)+------------------------+ ~0x0150–0x01DF| Геометрия кабинетов | (ширины/высоты и координаты)+------------------------+ конец файла| JSON с областями | (Irregular / Polygon Screen)+------------------------+
Дальше — как всё это руками читать и трогать.
.scr0x0000).Там должны быть первые 4 байта:
44 53 43 49
В правой (текстовой) части редактора это будет DSCI — сигнатура Screen Connection.
Если там что-то совсем другое — либо файл не .scr, либо формат сильно другой.
В заголовке есть ширина/высота входного сигнала (не сам экран, а то, что приходит на контроллер).
Найди смещения:
input width)input height)Эти числа хранятся в формате little-endian (LE):
Например, в hex:
0x003C: 80 070x003E: 38 04
Считаем:
ширина:
0x0780 = 1920высота:
0x0438 = 1080То есть входное разрешение 1920×1080.
Менять это руками можно, но опасно — проще через NovaLCT. Мы сейчас смотрим, чтобы понимать, с каким сигналом файл рассчитывался работать.
Чуть дальше по файлу лежит информация о том, на сколько «клеточек» разбит экран.
Идём к смещению примерно 0x0144:
Big-endian в переводе на человеческий: байты идут «старший → младший».
Например:
0x0144: 00 030x0146: 00 03
Расшифровка:
0x0003 = 30x0003 = 3То есть сетка 3×3 — всего 9 кабинетов.
Если там 00 04 и 00 02 — значит 4 столбца и 2 строки (то есть 8 кабинетов).
Количество кабинетов:
N = rows * cols
Это число потом будем использовать в таблице маршрута.
Самая интересная часть: как Nova понимает маршрут.
Nova не хранит в файле цепочку «1→2→3→…» напрямую.
Она просто знает:
То есть вся магия маршрута — в том, какие номера лежат в каких клетках.
Старт таблицы:
OFFSET_CARD_TABLE_START = 0x014B
Каждая следующая ячейка лежит через 17 (0x11) байт.
Нумерация логических ячеек j идёт по столбцам сверху вниз (это важно!):
Формулы (row/col считаем с 1):
j = (col - 1) * rows + (row - 1) col = j // rows + 1row = j % rows + 1
Чтение:
offset_j = 0x014B + 0x11 * jbyte = file[offset_j] # число 0..N-1card_id = byte + 1 # реальный номер карты 1..N
Допустим, мы посмотрели по адресам и получили:
rows = 3, cols = 3 → N = 9.Теперь расписываем j от 0 до 8:
| j | row | col | offset |
|---|---|---|---|
| 0 | 1 | 1 | 0x014B + 0x11*0 |
| 1 | 2 | 1 | 0x014B + 0x11*1 |
| 2 | 3 | 1 | 0x014B + 0x11*2 |
| 3 | 1 | 2 | 0x014B + 0x11*3 |
| 4 | 2 | 2 | 0x014B + 0x11*4 |
| 5 | 3 | 2 | 0x014B + 0x11*5 |
| 6 | 1 | 3 | 0x014B + 0x11*6 |
| 7 | 2 | 3 | 0x014B + 0x11*7 |
| 8 | 3 | 3 | 0x014B + 0x11*8 |
Представь, что ты выписал все эти байты (в виде десятичного числа) и добавил к каждому единицу — получил настоящие номера карт.
Потом переносишь их в табличку (строки и столбцы) — и видишь ту же картинку, что в NovaLCT.
Например, вышла матрица:
1 2 36 5 47 8 9
Это и есть тот маршрут, который ты видел в файлах 8/10: классическая змейка.
Теперь самое вкусное: как руками перерисовать маршрут, ничего не ломая.
Вытащить rows, cols, N.
Нарисовать табличку rows × cols на бумажке.
Заполнить её числами от 1 до N так, как должен идти кабель в жизни:
Посчитать для каждой клетки (row, col) индекс j.
Для каждого j:
offset_j = 0x014B + 0x11 * j,card_id - 1.Хотим:
1 2 36 5 47 8 9
Значит:
Считаем j и offset для каждого:
j=(1-1)*3+(1-1)=0 → offset=0x014B+0x11*0j=(1-1)*3+(2-1)=1 → offset=0x014B+0x11*1j=(1-1)*3+(3-1)=2 → …В hex-редакторе:
по каждому offset ищешь байт и меняешь его на card_id - 1:
00,01,02,08.Сохраняешь файл → загружаешь в NovaLCT → видишь новую схему и новый маршрут.
Важно: тут уже осторожнее, можно легко сделать экран некорректным.
В регулярных случаях (все модули одинаковые) в районе 0x0154 лежит:
0x0154–0x0155 : baseWidth (BE)0x0156–0x0157 : baseHeight (BE)
Например:
00 80 00 80,00 64 00 64.Если у тебя простой экран «чёткая сетка, все одинаковые»:
Но на практике:
Для ручной правки:
Перевести нужный размер в hex и big-endian:
00 C0,01 00,00 C8.Записать новые значения в 0x0154–0x0157.
Проверить, что контроллер такой файл принимает.
Если одна карта отличается по размеру (например, 150×300, или 128×200), то:
0x0150–0x01DF как отдельные LE-слова.Алгоритм «по-бедняцки»:
Запомнить текущий размер нестандартной карты, например:
Перевести в hex (LE):
96 00,2C 01.В hex-редакторе поискать по файлу байтовую последовательность 96 00 2C 01 примерно в районе 0x0150–0x01DF.
Проверить, что это действительно относится к нужной карте (сравнить с другим .scr, где только это изменено).
Заменить нужные значения на новые.
Но:
Здесь у нас нет полной официальной схемы всех полей. Это хирургия «на глаз».
Если есть возможность — такие вещи намного безопаснее делать в NovaLCT и только потом использовать.scrкак результат.
В самом конце файла можно увидеть в правой (текстовой) части hex-редактора что-то такое:
[{"si":0,"x1":0,"y1":0,"x2":0,"y2":0,"x3":0,"y3":0,"x4":0,"y4":0}]
Это:
x1..y4 — координаты четырёх точек многоугольника;si — индекс области.Если экран «обычный прямоугольный», там обычно все нули.
Ручная правка этого JSON — отдельная боль, и, честно, лучше работать с этим только через NovaLCT, когда включаешь режим Irregular Screen.
Complete technical specification derived from reverse engineering Nova.LCT.GigabitSystem.LEDScreenConfig.dll
Extension: .scr
Purpose: LED Screen Configuration for NovaLCT
Magic Header: DSCI (0x44534349)
Endianness: Little-endian
Total Size: Variable (depends on screen configuration)
Structure:
[Main Header: 54 bytes]
[DVI Section: 128 bytes]
[Screen Section: Variable]
[Adjust Section: 173 bytes]
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 4 | ASCII | Magic | Always "DSCI" (0x44534349) |
| 0x04 | 2 | uint16 | MainCRC | Checksum of main sections (see algorithm) |
| 0x06 | 4 | uint32 | DVILength | Length of DVI section (always 128) |
| 0x0A | 4 | uint32 | ScreenLength | Length of Screen section (variable) |
| 0x0E | 2 | uint16 | AdjustLength | Length of Adjust section (always 173) |
| 0x10 | 38 | bytes | Reserved | Padding (zeros) |
CRC Calculation:
# Main CRC covers: from offset 6 to end of Screen section
crc_range = header(54) + dvi(128) + screen(len) - 6
main_crc = sum(data[6:6+crc_range]) & 0xFFFF
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 2 | uint16 | Version | DVI data version (1001) |
| 0x02 | 2 | uint16 | CRC | Checksum from offset 4 to end |
| 0x04 | 1 | uint8 | DVIPortCols | DVI port columns (usually 1) |
| 0x05 | 1 | uint8 | DVIPortRows | DVI port rows (usually 1) |
| 0x06 | 2 | uint16 | GraphicsWidth | Total screen width in pixels (e.g., 1920) |
| 0x08 | 2 | uint16 | GraphicsHeight | Total screen height in pixels (e.g., 1080) |
| 0x0A | 118 | bytes | Reserved | Padding (zeros) |
Important: GraphicsWidth/Height should be total screen resolution, not grid size!
CRC Calculation:
dvi_crc = sum(dvi_data[4:128]) & 0xFFFF
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 2 | uint16 | Version | Screen data version (1006 = ScreenDataVer+1) |
| 0x02 | 2 | uint16 | CRC | Checksum from offset 4 to end of section |
| 0x04 | 6 | bytes | Reserved1 | Padding |
| 0x0A | 16 | bytes | DVIOffsets | DVI offset data (4 DVIs × 4 bytes, usually zeros) |
| 0x1A | 2 | bytes | Reserved2 | Padding |
| 0x1C | 2 | uint16 | ScreenTotalSize | Total size of Screen section |
| 0x1E | 6 | bytes | Reserved3 | Padding |
| 0x84 | 1 | uint8 | ScreenCount | Number of screens (always 1) |
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x85 | 4 | uint32 | ScreenDataLength | Length of following screen data block |
For StandardLEDDisplayInfo (Type=1):
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 1 | uint8 | Type | Display type (1=Standard) |
| 0x01 | 1 | uint8 | VirtualMode | Virtual mode (0=Disable) |
| 0x02 | 2 | uint16 | X | Screen X position |
| 0x04 | 2 | uint16 | Y | Screen Y position |
| 0x06 | 2 | uint16 | ScanBoardCols | Number of cabinet columns |
| 0x08 | 2 | uint16 | ScanBoardRows | Number of cabinet rows |
Followed by Card Data (17 bytes per card):
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 1 | uint8 | SenderIndex | Sending card index (usually 0) |
| 0x01 | 1 | uint8 | PortIndex | Port index (always 0 for single port) |
| 0x02 | 2 | uint16 | ConnectIndex | Connection index (see pattern below) |
| 0x04 | 2 | uint16 | X | Cabinet X pixel position |
| 0x06 | 2 | uint16 | Y | Cabinet Y pixel position |
| 0x08 | 2 | uint16 | XInPort | Cabinet column index in grid |
| 0x0A | 2 | uint16 | YInPort | Cabinet row index in grid |
| 0x0C | 2 | uint16 | Width | Cabinet width in pixels (e.g., 128) |
| 0x0E | 2 | uint16 | Height | Cabinet height in pixels (e.g., 128) |
| 0x10 | 1 | uint8 | DVIIndex | DVI port index (always 1) |
ConnectIndex Pattern (for 3×3 configuration):
Column-major order with special pattern:
Cards: [0,5,6,1,4,7,2,3,8]
Grid layout (col, row) → ConnectIndex:
(0,0)→0, (0,1)→5, (0,2)→6,
(1,0)→1, (1,1)→4, (1,2)→7,
(2,0)→2, (2,1)→3, (2,2)→8
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 2 | uint16 | JSONLength | Length of JSON string (66 for standard) |
| 0x02 | var | ASCII | JSONData | JSON metadata string |
Standard JSON:
[{"si":0,"x1":0,"y1":0,"x2":0,"y2":0,"x3":0,"y3":0,"x4":0,"y4":0}]
Screen Section CRC:
screen_crc = sum(screen_data[4:total_length]) & 0xFFFF
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 2 | uint16 | Version | Adjust params version (1002) |
| 0x02 | 2 | uint16 | CRC | Checksum from offset 4 to end |
| 0x04 | 128 | bytes | Reserved | Padding (zeros) |
| 0x84 | 1 | uint8 | ScreenCount | Number of screens (always 1) |
Per-screen parameters (40 bytes, starts at offset 0x85):
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0x00 | 1 | uint8 | ScreenXZoomType | X zoom type (0) |
| 0x01 | 1 | uint8 | ScreenXScale | X scale (1, not 100!) |
| 0x02 | 1 | uint8 | ScreenYZoomType | Y zoom type (0) |
| 0x03 | 1 | uint8 | ScreenYScale | Y scale (1, not 100!) |
| 0x04 | 1 | uint8 | VirtualMap | Virtual mapping (228/0xE4) |
| 0x05 | 20 | UTF-16LE | ScreenName | Screen name (usually empty) |
| 0x19 | 1 | uint8 | ThreeD | 3D mode (0) |
| 0x1A | 14 | bytes | Reserved | Padding (zeros) |
Adjust Section CRC:
adjust_crc = sum(adjust_data[4:173]) & 0xFFFF
NOT a standard CRC! It's a simple sum of bytes:
def calculate_crc(data: bytes, offset: int, count: int) -> int:
"""
NovaLCT "CRC" algorithm - simple byte summation
Args:
data: Byte array
offset: Starting offset
count: Number of bytes to sum
Returns:
16-bit checksum (sum & 0xFFFF)
"""
crc = 0
for i in range(count):
if offset + i < len(data):
crc += data[offset + i]
return crc & 0xFFFF
Applied to:
| Section | Field | Value | Notes |
|---|---|---|---|
| DVI | DVIDataVer | 1001 | Fixed value |
| Screen | ScreenDataVer | 1006 | = 1005 + 1 |
| Adjust | ScrAdjustParamsVer | 1002 | Fixed value |
Important: Screen version is stored as ScreenDataVer + 1 in the file!
| Field | Common Mistake | Correct Value |
|---|---|---|
| PortIndex | Sequential (0,1,2…) | All 0 (single port) |
| DVIIndex | 0 | 1 (DVI port 1) |
| ScreenXScale | 100 | 1 (no scaling) |
| ScreenYScale | 100 | 1 (no scaling) |
| VirtualMap | 30 | 228 (0xE4) |
| Graphics WxH | Grid size | Full screen res (1920×1080) |
| Screen Version | 1002 | 1006 (1005+1) |
Total Size: 723 bytes
├─ Main Header: 54 bytes
├─ DVI Section: 128 bytes
├─ Screen Section: 368 bytes
│ ├─ Header: 137 bytes (133 + 4 length)
│ ├─ Screen Data: 163 bytes (10 header + 9 cards × 17)
│ └─ JSON Metadata: 68 bytes (2 length + 66 data)
└─ Adjust Section: 173 bytes
Cabinets: 9 (3 columns × 3 rows)
Cabinet Size: 128×128 pixels
Total Resolution: 384×384 pixels
DVI Graphics: 1920×1080 pixels (full screen)
Create Main Header (54 bytes)
Build DVI Section (128 bytes)
Build Screen Section
Build Adjust Section (173 bytes)
Calculate Main CRC
✅ Magic header is "DSCI"
✅ All section lengths are correct
✅ DVI Graphics is full screen resolution (not grid size)
✅ Screen Version is 1006 (not 1002)
✅ All PortIndex values are 0
✅ All DVIIndex values are 1
✅ ConnectIndex follows correct pattern
✅ Scale values are 1 (not 100)
✅ VirtualMap is 228 (not 30)
✅ All CRCs are calculated correctly
✅ JSON metadata is present in Screen section
✅ File size matches expected total
SoftWareSpaceAnalyser.DviScreenInfoToArray()SoftWareSpaceAnalyser.CaculateCRC()SoftWareSpaceAnalyser1600New.ScreenInfoToArray1600()