Данный раздел содержит реализацию логики различных устройств ввода/вывода для NES/Famicom/Dendy, портов расширения и сопроводительных чипов.
TBD: порты расширения пока не реализованы и не рассматриваются.
Обзор
Как может показаться - что может быть сложного в обычном контроллере? Однако...
- Соединение контроллеров различается у NES/Famicom/Dendy. NES/Dendy использует соединение через порты. В Famicom контроллеры неразъёмно соединены с основным юнитом.
- На втором контроллере Famicom есть микрофон (да, такой вот wtf из 90-х).
- На контроллерах Dendy есть Turbo-кнопки
- Количество портов, их тип и реализация I/O на материнской плате различается между всеми консолями
И как всё это красиво обобщить? :-)
Архитектура IO-подсистемы
- Реализация логики устройств находится в нативном коде
- Каждое устройство ввода снабжается уникальным идентификатором, полностью определяющим его модель и реализацию (
DeviceID); список всех идентификаторов вio.h - Описание материнской платы в
BoardDescription.jsonсодержит список портов (IOPort). Каждый порт содержит список ID устройств, которые можно к нему подключить (Attach/Detach) - IO-подсистема содержит фабрику для создания устройств по их
DeviceID. Созданное устройство определяется описателем, целым числом >= 0 (Handle) - Каждое устройство предоставляет список своих контролов ввода/вывода (
IOState). КаждыйIOStateопределяется целым числом >= 0 - Устройство содержит метод
SetState, с помощью которого потребитель может устанавливать состояния контроловIOState
Настройка IO-устройств
Со стороны эмулятора настройка сводится к привязке IOState к какому-то API для работы с устройствами ввода на стороне ПК. Затем ассоциативная связь используется для установки SetState подключённого устройства.
Эмулятор содержит пул зарегистрированных устройств с указанием DeviceID (устройства добавляет и удаляет пользователь).
Далее каждое устройство можно настроить (грубо говоря, сделать привязку IOState к DirectInput/XInput или ещё какому-то API). У каждого типа устройств свой диалог привязки кнопок/прочих актуаторов.
После настройки устройство можно подключить или отключить к указанной модели материнской платы (Attach / Detach). После запуска эмуляции для IO-подсистемы будут созданы экземпляры подключённых устройств, подключены к портам, и далее эмулятор может вызывать со своей стороны SetState, чтобы нативная реализация преобразовывала IOState в конкретные сигналы для портов ввода/вывода.
Опять же, описание весьма абстрактное — по ходу работ оно кристаллизуется в конкретные классы/методы.
Абстрактное устройство
DeviceID: 0x00000000
Не содержит никаких IOState.
Famicom Controller (Port1)
DeviceID: 0x00000001
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | Select | 0/1 |
| 5 | Start | 0/1 |
| 6 | B | 0/1 |
| 7 | A | 0/1 |
Контроллеры Famicom, хотя внешне и несъёмные, но внутри корпуса их всё же можно отсоединить, поэтому для унификации кода будем считать их съёмными.
Famicom Controller (Port2)
DeviceID: 0x00000002
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | B | 0/1 |
| 5 | A | 0/1 |
| 6 | Volume | 0...255 |
| 7 | MicLevel | 0...255 |
Значения громкости и уровня микрофона пока в диапазоне UInt8 — посмотрим, насколько хороша эта модель в процессе.
NES Controller (Port1/2)
DeviceID: 0x00000003
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | Select | 0/1 |
| 5 | Start | 0/1 |
| 6 | B | 0/1 |
| 7 | A | 0/1 |
Dendy Turbo Controller (Port1/2)
DeviceID: 0x00000004
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | Select | 0/1 |
| 5 | Start | 0/1 |
| 6 | TurboB | 0/1 |
| 7 | TurboA | 0/1 |
| 8 | B | 0/1 |
| 9 | A | 0/1 |
Virtual Famicom Controller (Port1)
DeviceID: 0x00010001
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | Select | 0/1 |
| 5 | Start | 0/1 |
| 6 | B | 0/1 |
| 7 | A | 0/1 |
Virtual Famicom Controller (Port2)
DeviceID: 0x00010002
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | B | 0/1 |
| 5 | A | 0/1 |
| 6 | Volume | 0...255 |
| 7 | MicLevel | 0...255 |
Virtual NES Controller (Port1/2)
DeviceID: 0x00010003
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | Select | 0/1 |
| 5 | Start | 0/1 |
| 6 | B | 0/1 |
| 7 | A | 0/1 |
Virtual Dendy Turbo Controller (Port1/2)
DeviceID: 0x00010004
| IOState | Actuator | Values |
|---|---|---|
| 0 | Up | 0/1 |
| 1 | Down | 0/1 |
| 2 | Left | 0/1 |
| 3 | Right | 0/1 |
| 4 | Select | 0/1 |
| 5 | Start | 0/1 |
| 6 | TurboB | 0/1 |
| 7 | TurboA | 0/1 |
| 8 | B | 0/1 |
| 9 | A | 0/1 |