476 lines
13 KiB
Markdown
476 lines
13 KiB
Markdown
# Пример простого плагина для WC
|
||
|
||
## Минимальный плагин "Hello World"
|
||
|
||
Этот пример показывает базовую структуру плагина для Wild Commander.
|
||
|
||
```asm
|
||
;---------------------------------------
|
||
; Hello World Plugin
|
||
; Простейший пример плагина для WC
|
||
;---------------------------------------
|
||
|
||
ORG #6000
|
||
|
||
; Константы WC API
|
||
WLD EQU #6006 ; Точка вызова API
|
||
ABT EQU #6004 ; Флаг ESC
|
||
ENT EQU #6005 ; Флаг ENTER
|
||
|
||
;---------------------------------------
|
||
; ЗАГОЛОВОК ПЛАГИНА (512 байт)
|
||
;---------------------------------------
|
||
|
||
; +0: Резерв (16 байт)
|
||
DS 16
|
||
|
||
; +16: Идентификатор (16 байт)
|
||
DB "WildCommanderMDL"
|
||
|
||
; +32: Версия формата
|
||
DB #10
|
||
|
||
; +33: Резерв
|
||
DB 0
|
||
|
||
; +34: Количество страниц
|
||
DB 1 ; Плагин занимает 1 страницу
|
||
|
||
; +35: Стартовая страница
|
||
DB 0 ; Запускаться будет со страницы 0
|
||
|
||
; +36: Блоки данных (6 x 2 байта)
|
||
DB 0,1 ; Блок 0: код, 1x512 байт
|
||
DB 0,0
|
||
DB 0,0
|
||
DB 0,0
|
||
DB 0,0
|
||
DB 0,0
|
||
|
||
; +48: Резерв (15 байт)
|
||
DS 15
|
||
|
||
; +63: Флаги расширений
|
||
DB 0 ; Стандартный режим
|
||
|
||
; +64: Блок расширений (32 x 3 байта)
|
||
DS 32*3 ; Не используем расширения
|
||
|
||
; +160: Конец блока расширений
|
||
DB 0
|
||
|
||
; +161: Максимальный размер файла (4 байта)
|
||
DW #FFFF,#FFFF ; Без ограничений
|
||
|
||
; +165: Имя плагина (32 байта)
|
||
DB "Hello World Plugin "
|
||
|
||
; +197: Тип условий вызова
|
||
DB #03 ; Из меню (F10)
|
||
|
||
; +198: Текст для F2/F4 (6 байт)
|
||
DS 6
|
||
|
||
; +204: Текст для меню вьювера (24 байта)
|
||
DS 24
|
||
|
||
; +228: Резерв (32 байта)
|
||
DS 32
|
||
|
||
; +260: Параметры INI (252 байта)
|
||
DB 0 ; Нет параметров
|
||
DS 251
|
||
|
||
;---------------------------------------
|
||
; КОД ПЛАГИНА
|
||
;---------------------------------------
|
||
|
||
ORG #8000,#6200
|
||
|
||
;---------------------------------------
|
||
; Точка входа плагина
|
||
;---------------------------------------
|
||
PLUGIN:
|
||
; Сохранить входные параметры
|
||
LD (SAVE_IX),IX
|
||
LD (SAVE_HL),HL
|
||
LD (SAVE_DE),DE
|
||
LD (SAVE_BC),BC
|
||
LD (SAVE_A),A
|
||
|
||
; Восстановить текстовый режим
|
||
CALL GEDPL
|
||
|
||
; Показать окно
|
||
LD IX,WINDOW
|
||
CALL PRWOW
|
||
|
||
; Вывести текст
|
||
LD HL,TEXT
|
||
LD DE,#0102 ; Y=1, X=2
|
||
LD BC,12 ; Длина
|
||
CALL PRSRW
|
||
|
||
; Применить цвет
|
||
EXA
|
||
LD A,%00001111 ; Синий на белом
|
||
CALL PRIAT
|
||
EXA
|
||
|
||
; Главный цикл
|
||
MAIN_LOOP:
|
||
EI
|
||
HALT ; Ждем прерывание
|
||
|
||
; Проверка ESC
|
||
CALL ESC_KEY
|
||
JR Z,MAIN_LOOP
|
||
|
||
; Выход
|
||
LD IX,WINDOW
|
||
CALL RRESB ; Стереть окно
|
||
|
||
XOR A ; A=0: просто выход
|
||
RET
|
||
|
||
;---------------------------------------
|
||
; WC API функции
|
||
;---------------------------------------
|
||
|
||
PRWOW: ; Вывод окна
|
||
LD A,1
|
||
JP WLD
|
||
|
||
RRESB: ; Стирание окна
|
||
LD A,2
|
||
JP WLD
|
||
|
||
PRSRW: ; Печать строки
|
||
LD A,3
|
||
JP WLD
|
||
|
||
PRIAT: ; Установка цвета
|
||
EXA
|
||
LD A,4
|
||
JP WLD
|
||
|
||
ESC_KEY: ; Проверка ESC
|
||
LD A,#17
|
||
JP WLD
|
||
|
||
GEDPL: ; Восстановление TXT режима
|
||
LD A,#0F
|
||
JP WLD
|
||
|
||
;---------------------------------------
|
||
; Данные
|
||
;---------------------------------------
|
||
|
||
; Структура окна
|
||
WINDOW:
|
||
DB 0 ; Флаги
|
||
DB 0 ; Маска курсора
|
||
DB 255 ; X (центр)
|
||
DB 255 ; Y (центр)
|
||
DB 30 ; Ширина
|
||
DB 5 ; Высота
|
||
DB %01111111 ; Цвет (белый на синем)
|
||
DB 0 ; Резерв
|
||
DW #0000 ; Буфер
|
||
DB 0,0 ; Разделители
|
||
|
||
; Текст
|
||
TEXT:
|
||
DB "Hello World!"
|
||
|
||
; Сохраненные регистры
|
||
SAVE_A: DB 0
|
||
SAVE_BC: DW 0
|
||
SAVE_DE: DW 0
|
||
SAVE_HL: DW 0
|
||
SAVE_IX: DW 0
|
||
|
||
END
|
||
```
|
||
|
||
## Компиляция
|
||
|
||
Используйте sjasm для компиляции:
|
||
|
||
```batch
|
||
sjasm hello.asm hello.wmf
|
||
```
|
||
|
||
## Пояснения к коду
|
||
|
||
### Структура
|
||
|
||
1. **Заголовок (#6000-#61FF)** - 512 байт метаданных
|
||
2. **Код (#8000+)** - исполняемый код плагина
|
||
|
||
### Ключевые моменты
|
||
|
||
1. **GEDPL обязателен!** Вызывайте его в начале, чтобы восстановить текстовый режим.
|
||
|
||
2. **Структура окна** должна быть в памяти плагина (#8000-#BFFF).
|
||
|
||
3. **Главный цикл** должен включать:
|
||
- `EI` - разрешить прерывания
|
||
- `HALT` - ждать прерывание (экономит энергию)
|
||
- Проверку клавиш
|
||
|
||
4. **Выход** через `RET` с кодом в A.
|
||
|
||
## Пример с файлом
|
||
|
||
```asm
|
||
;---------------------------------------
|
||
; Text Viewer Plugin - просмотр текстовых файлов
|
||
;---------------------------------------
|
||
|
||
ORG #6000
|
||
|
||
WLD EQU #6006
|
||
|
||
; ... Заголовок плагина ...
|
||
|
||
; +64: Расширения
|
||
DB "TXT" ; .txt файлы
|
||
DB "DOC" ; .doc файлы
|
||
DS 30*3
|
||
|
||
; +197: Тип вызова
|
||
DB #00 ; По расширению
|
||
|
||
ORG #8000,#6200
|
||
|
||
;---------------------------------------
|
||
; Точка входа
|
||
;---------------------------------------
|
||
PLUGIN:
|
||
; Проверить тип вызова
|
||
CP #00 ; По расширению?
|
||
JR Z,VIEW_FILE
|
||
|
||
; Другой тип вызова - не наш файл
|
||
LD A,1 ; Файл не опознан
|
||
RET
|
||
|
||
VIEW_FILE:
|
||
; Сохранить параметры
|
||
LD (FILESIZE),HL
|
||
LD (FILESIZE+2),DE
|
||
LD (FILENAME),BC
|
||
LD (PANEL_IX),IX
|
||
|
||
; Восстановить TXT режим
|
||
CALL GEDPL
|
||
|
||
; Загрузить начало файла
|
||
LD HL,BUFFER
|
||
LD B,16 ; 16 блоков = 8KB
|
||
CALL LOAD512
|
||
|
||
; Показать окно
|
||
LD IX,WINDOW
|
||
CALL PRWOW
|
||
|
||
; Вывести текст
|
||
LD HL,BUFFER
|
||
LD DE,#0101
|
||
LD BC,1000
|
||
CALL PRSRW
|
||
|
||
; Цикл просмотра
|
||
VIEW_LOOP:
|
||
EI
|
||
HALT
|
||
|
||
CALL ESC_KEY
|
||
JR Z,VIEW_LOOP
|
||
|
||
; Выход
|
||
LD IX,WINDOW
|
||
CALL RRESB
|
||
XOR A
|
||
RET
|
||
|
||
;---------------------------------------
|
||
; WC API
|
||
;---------------------------------------
|
||
|
||
LOAD512: LD A,#30
|
||
JP WLD
|
||
|
||
PRWOW: LD A,1
|
||
JP WLD
|
||
|
||
RRESB: LD A,2
|
||
JP WLD
|
||
|
||
PRSRW: LD A,3
|
||
JP WLD
|
||
|
||
ESC_KEY: LD A,#17
|
||
JP WLD
|
||
|
||
GEDPL: LD A,#0F
|
||
JP WLD
|
||
|
||
;---------------------------------------
|
||
; Данные
|
||
;---------------------------------------
|
||
|
||
WINDOW:
|
||
DB 0
|
||
DB 0
|
||
DB 0 ; X=0
|
||
DB 0 ; Y=0
|
||
DB 80 ; На весь экран
|
||
DB 25
|
||
DB %01111111
|
||
DB 0
|
||
DW #0000
|
||
DB 0,0
|
||
|
||
BUFFER: DS 8192 ; Буфер для файла
|
||
|
||
FILENAME: DW 0
|
||
FILESIZE: DS 4
|
||
PANEL_IX: DW 0
|
||
|
||
END
|
||
```
|
||
|
||
## Следующий шаг
|
||
|
||
Изучите примеры в каталоге PlugRef/:
|
||
- **wild_player/** - полноценный музыкальный плеер
|
||
- **gsplayer/** - плеер для GS
|
||
- **txt_editor/** - редактор текста
|
||
|
||
---
|
||
|
||
## Минимальный плагин на SDCC C
|
||
|
||
> Компилятор: SDCC 4.x, ключи `--opt-code-size -mz80 --sdcccall 1`
|
||
> Источник соглашений: `src_sdcc/inc/wc_api.h`
|
||
|
||
```c
|
||
/*
|
||
* hello_plugin.c — минимальный WC-плагин на SDCC C
|
||
*
|
||
* Сборка:
|
||
* sdcc --opt-code-size -mz80 --sdcccall 1 --code-loc 0x8000 \
|
||
* --data-loc 0xB800 -I inc hello_plugin.c wc_api.s crt0.s
|
||
* makebin -s 16384 hello_plugin.ihx hello_plugin.bin
|
||
* pack hello_plugin.bin HELLO.WMF # добавить заголовок WMF
|
||
*/
|
||
|
||
#include "inc/wc_api.h"
|
||
#include "inc/types.h"
|
||
|
||
/* ─────────────────────────────────────────────────
|
||
* DATA: размещается в #B800+ (--data-loc 0xB800)
|
||
* ──────────────────────────────────────────────── */
|
||
|
||
/* Окно в секции DATA (адрес в #8000–#BFFF — обязательно!) */
|
||
static wc_window_t g_win;
|
||
|
||
/* Буфер вывода */
|
||
static char g_buf[64];
|
||
|
||
/*
|
||
* wc_exit_code — специальная переменная: WC читает A при
|
||
* выходе из main(). Значения: 0=ESC, 2=след. файл, 3=reload dir.
|
||
*/
|
||
uint8_t wc_exit_code;
|
||
|
||
/* ─────────────────────────────────────────────────
|
||
* CODE: размещается в #8000+ (--code-loc 0x8000)
|
||
* ──────────────────────────────────────────────── */
|
||
|
||
int main(void) {
|
||
/* 1. Восстановить дисплей WC (TXT-режим, панели)
|
||
* ⚠ Затирает #1000–#1447 в окне 0 */
|
||
wc_gedpl();
|
||
|
||
/* 2. Настроить окно */
|
||
g_win.type = WC_WIN_SINGLE; /* однолинейная рамка */
|
||
g_win.cur_mask = 0x07;
|
||
g_win.x = 255; /* 255 = по центру */
|
||
g_win.y = 255;
|
||
g_win.width = 44;
|
||
g_win.height = 6;
|
||
g_win.color = WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE);
|
||
g_win.buf_addr = 0x0000; /* 0 = автосохранение фона в RESB */
|
||
|
||
/* 3. Нарисовать окно */
|
||
wc_prwow(&g_win);
|
||
|
||
/* 4. Вывести текст (разметка WC: 0x0E = центрирование) */
|
||
wc_txtpr(&g_win, "\x0eHello from C plugin!", 1, 1);
|
||
wc_txtpr(&g_win, "\x0e\x03Press ESC to exit", 3, 1);
|
||
|
||
/* 5. Главный цикл */
|
||
while (1) {
|
||
if (wc_key_esc()) break;
|
||
if (wc_key_enter()) break;
|
||
|
||
/* пример: цикл ожидания клавиши без блокировки */
|
||
/* uint8_t k = wc_kbscn(WC_KBSCN_NORMAL); */
|
||
}
|
||
|
||
/* 6. Убрать окно и восстановить дисплей */
|
||
wc_rresb(&g_win);
|
||
wc_gedpl();
|
||
|
||
/* 7. Код выхода */
|
||
wc_exit_code = 0; /* 0 = нормальный выход */
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
### Полезные паттерны
|
||
|
||
**Загрузка файла в мегабуфер:**
|
||
```c
|
||
void load_file_to_megabuf(void) {
|
||
uint8_t page = 0;
|
||
uint8_t rc;
|
||
|
||
wc_stream(WC_STREAM_ROOT); // открыть файловый поток
|
||
// wc_fentry() уже вызван WC перед стартом плагина
|
||
|
||
do {
|
||
/* ⚠ После любого wc_* Win3 сбрасывается — переключаем снова */
|
||
wc_mngcvpl(page);
|
||
rc = wc_load512(0xC000, 32); // читать 16 КБ
|
||
page++;
|
||
} while (rc == 0 && page < 64);
|
||
/* rc == WC_EOF (0x0F) = конец файла, rc != 0 && != 0x0F = ошибка */
|
||
}
|
||
```
|
||
|
||
**Установка собственного ISR:**
|
||
```c
|
||
extern void my_isr(void); // ISR написан на ASM в asm/isr.s
|
||
|
||
void setup_isr(void) {
|
||
wc_int_pl(WC_INT_PLUGIN); // режим плагина
|
||
wc_int_pl_handler((uint16_t)my_isr); // задать адрес
|
||
/* Теперь my_isr() вызывается ~1367 раз/сек */
|
||
}
|
||
|
||
void restore_isr(void) {
|
||
wc_int_pl(WC_INT_DISABLE_ALL); // вернуть управление WC
|
||
}
|
||
```
|
||
|
||
**Переключение CPU на 14 МГц:**
|
||
```c
|
||
wc_turbopl(WC_TURBO_CPU, 0x02); // 14 МГц
|
||
// ...работа...
|
||
wc_turbopl(WC_TURBO_RESTORE, 0); // восстановить
|
||
```
|