コレコビジョンコアでHVC-051キーパッドをつかう

MiSTer FPGAコレコビジョンコアでHVC-051キーパッドを使いたい! github.com

まずはスーパーファミコンをUSB化する基板をプリントします。 github.com

NES拡張端子15ピンの1、12、13、14、15をSFCコントローラ端子に結線します。 www.raphnet.net

まずは以下のファームウェアに書き換えてNESとして動作確認します。 github.com

デフォルトでは4ボタン(SELECT、START、A、B)しか認識しないので、ソースコードを若干修正しました。

NESControllerUSB.ino

/*  DaemonBite NES Controllers to USB Adapter
 *  Author: Mikael Norrgård <mick@daemonbite.com>
 *
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *  
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */

#include "Gamepad.h"

// ATT: 20 chars max (including NULL at the end) according to Arduino source code.
// Additionally serial number is used to differentiate arduino projects to have different button maps!
const char *gp_serial = "NES to USB";

//#define DEBUG

#define GAMEPAD_COUNT 1      // NOTE: No more than TWO gamepads are possible at the moment due to a USB HID issue.
#define GAMEPAD_COUNT_MAX 4  // NOTE: For some reason, can't have more than two gamepads without serial breaking. Can someone figure out why?
                             //       (It has something to do with how Arduino handles HID devices)
#define BUTTON_COUNT      24 /// 8 // Standard NES controller has four buttons and four axes, totalling 8
#define BUTTON_READ_DELAY 20 // Delay between button reads in µs
#define MICROS_LATCH       8 // 12µs according to specs (8 seems to work fine)
#define MICROS_CLOCK       4 //  6µs according to specs (4 seems to work fine)
#define MICROS_PAUSE       4 //  6µs according to specs (4 seems to work fine)

#define UP    0x01
#define DOWN  0x02
#define LEFT  0x04
#define RIGHT 0x08

// Wire it all up according to the following table:
//
// NES           SNES        Arduino Pro Micro
// --------------------------------------
// VCC                       VCC (All gamepads)
// GND                       GND (All gamepads)
// OUT0 (LATCH)              2   (PD1, All gamepads)
// CUP  (CLOCK)              3   (PD0, All gamepads)
// D1   (GP1: DATA)          A0  (PF7, Gamepad 1) 
// D1   (GP2: DATA)          A1  (PF6, Gamepad 2)
// D1   (GP3: DATA)          A2  (PF5, Gamepad 3, not currently used)
// D1   (GP4: DATA)          A3  (PF4, Gamepad 4, not currently used)

// Set up USB HID gamepads
Gamepad_ Gamepad[GAMEPAD_COUNT];

// Controllers
/// uint8_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0};
/// uint8_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint32_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint32_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000};
/// uint8_t btnBits[BUTTON_COUNT] = {0x20,0x10,0x40,0x80,UP,DOWN,LEFT,RIGHT};
uint32_t btnBits[BUTTON_COUNT] =
{
  0x00000020, // B
  0x00000010, // A
  0x00000040, // Select
  0x00000080, // Start

  0x00000001, // Up
  0x00000002, // Down
  0x00000004, // Left
  0x00000008, // Right

  0x00001000, // 0
  0x00002000, // 1
  0x00004000, // 2
  0x00008000, // 3
  0x00010000, // 4
  0x00020000, // 5
  0x00040000, // 6
  0x00080000, // 7
  0x00100000, // 8
  0x00200000, // 9
  0x00400000, // *
  0x00800000, // #
  0x01000000, // .
  0x02000000, // C
  0x04000000, // unused
  0x08000000  // END
};
uint8_t gp = 0;

// Timing
uint32_t microsButtons = 0;

#ifdef DEBUG
uint32_t microsStart = 0;
uint32_t microsEnd = 0;
uint8_t counter = 0;
#endif

void setup()
{
  // Setup latch and clock pins (2,3 or PD1, PD0)
  DDRD  |=  B00000011; // output
  PORTD &= ~B00000011; // low

  // Setup data pins (A0-A3 or PF7-PF4)
  DDRF  &= ~B11110000; // inputs
  PORTF |=  B11110000; // enable internal pull-ups

  #ifdef DEBUG
  Serial.begin(115200);
  delay(2000);
  #endif

  // Short delay to let controllers stabilize
  delay(50);
}

void loop() { while(1)
{
  // See if enough time has passed since last button read
  if((micros() - microsButtons) > BUTTON_READ_DELAY)
  {    
    #ifdef DEBUG
    microsStart = micros();
    #endif
    
    // Pulse latch
    sendLatch();

    for(uint8_t btn=0; btn<BUTTON_COUNT; btn++)
    {
      for(gp=0; gp<GAMEPAD_COUNT; gp++) 
        (PINF & gpBit[gp]) ? buttons[gp] &= ~btnBits[btn] : buttons[gp] |= btnBits[btn];
      sendClock();
    }

    for(gp=0; gp<GAMEPAD_COUNT; gp++)
    {
      // Has any buttons changed state?
      if (buttons[gp] != buttonsPrev[gp])
      {
        Gamepad[gp]._GamepadReport.buttons = (buttons[gp] >> 4); // First 4 bits are the axes
        Gamepad[gp]._GamepadReport.Y = ((buttons[gp] & DOWN) >> 1) - (buttons[gp] & UP);
        Gamepad[gp]._GamepadReport.X = ((buttons[gp] & RIGHT) >> 3) - ((buttons[gp] & LEFT) >> 2);
///        Gamepad[gp]._GamepadReport.buttons = buttons[gp];
        buttonsPrev[gp] = buttons[gp];
        Gamepad[gp].send();
      }
    }

    microsButtons = micros();

    #ifdef DEBUG
    microsEnd = micros();
    if(counter < 20) {
      Serial.println(microsEnd-microsStart);
      counter++;
    }
    #endif
  }
}}

void sendLatch()
{
  // Send a latch pulse to the NES controller(s)
  PORTD |=  B00000010; // Set HIGH
  delayMicroseconds(MICROS_LATCH);
  PORTD &= ~B00000010; // Set LOW
  delayMicroseconds(MICROS_PAUSE); 
}

void sendClock()
{
  // Send a clock pulse to the NES controller(s)
  PORTD |=  B10000001; // Set HIGH
  delayMicroseconds(MICROS_CLOCK);
  PORTD &= ~B10000001; // Set LOW
  delayMicroseconds(MICROS_PAUSE); 
}

GamePad.h

/*  Gamepad.h
 *   
 *  Based on the advanced HID library for Arduino: 
 *  https://github.com/NicoHood/HID
 *  Copyright (c) 2014-2015 NicoHood
 * 
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */

#pragma once

#include "HID.h"

extern const char* gp_serial;

typedef struct {
  uint32_t buttons;
  int8_t X;
  int8_t Y;  
} GamepadReport;
///typedef struct {
///  uint32_t buttons;
///} GamepadReport;

class Gamepad_ : public PluggableUSBModule
{  
  private:
    uint8_t reportId;

  protected:
    int getInterface(uint8_t* interfaceCount);
    int getDescriptor(USBSetup& setup);
    uint8_t getShortName(char *name);
    bool setup(USBSetup& setup);
    
    uint8_t epType[1];
    uint8_t protocol;
    uint8_t idle;
    
  public:
    GamepadReport _GamepadReport;
    Gamepad_(void);
    void reset(void);
    void send();
};

GamePad.cpp

/*  Gamepad.cpp
 *   
 *  Based on the advanced HID library for Arduino: 
 *  https://github.com/NicoHood/HID
 *  Copyright (c) 2014-2015 NicoHood
 * 
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */
#include "Gamepad.h"

static const uint8_t _hidReportDescriptor[] PROGMEM = {
  0x05, 0x01,                       // USAGE_PAGE (Generic Desktop)
  0x09, 0x04,                       // USAGE (Joystick) (Maybe change to gamepad? I don't think so but...)
  0xa1, 0x01,                       // COLLECTION (Application)
    0xa1, 0x00,                       // COLLECTION (Physical)
    
///      0x05, 0x09,                       // USAGE_PAGE (Button)
///      0x19, 0x01,                       // USAGE_MINIMUM (Button 1)
///      0x29, 0x04,                       // USAGE_MAXIMUM (Button 4)
///      0x15, 0x00,                       // LOGICAL_MINIMUM (0)
///      0x25, 0x01,                       // LOGICAL_MAXIMUM (1)
///      0x95, 0x04,                       // REPORT_COUNT (4)
///      0x75, 0x01,                       // REPORT_SIZE (1)
///      0x81, 0x02,                       // INPUT (Data,Var,Abs)

///      0x95, 0x01,                       // REPORT_COUNT (1) ; pad out the bits into a number divisible by 8
///      0x75, 0x04,                       // REPORT_SIZE (4)
///      0x81, 0x03,                       // INPUT (Const,Var,Abs)
      0x05, 0x09,                       // USAGE_PAGE (Button)
      0x19, 0x01,                       // USAGE_MINIMUM (Button 1)
      0x29, 0x18,                       // USAGE_MAXIMUM (Button 24)
      0x15, 0x00,                       // LOGICAL_MINIMUM (0)
      0x25, 0x01,                       // LOGICAL_MAXIMUM (1)
      0x95, 0x18,                       // REPORT_COUNT (24)
      0x75, 0x01,                       // REPORT_SIZE (1)
      0x81, 0x02,                       // INPUT (Data,Var,Abs)

      0x95, 0x01,                       // REPORT_COUNT (1)
      0x75, 0x08,                       // REPORT_SIZE (8)
      0x81, 0x03,                       // INPUT (Const,Var,Abs)

      0x05, 0x01,                       // USAGE_PAGE (Generic Desktop)
      0x09, 0x01,                       // USAGE (pointer)
      0xa1, 0x00,                       // COLLECTION (Physical) 
        0x09, 0x30,                       // USAGE (X)
        0x09, 0x31,                       // USAGE (Y)
        0x15, 0xff,                       // LOGICAL_MINIMUM (-1)
        0x25, 0x01,                       // LOGICAL_MAXIMUM (1)
        0x95, 0x02,                       // REPORT_COUNT (2)
        0x75, 0x08,                       // REPORT_SIZE (8)
        0x81, 0x02,                       // INPUT (Data,Var,Abs)
      0xc0,                             // END_COLLECTION

    0xc0,                             // END_COLLECTION
  0xc0,                             // END_COLLECTION 
};

Gamepad_::Gamepad_(void) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1)
{
  epType[0] = EP_TYPE_INTERRUPT_IN;
  PluggableUSB().plug(this);
}

int Gamepad_::getInterface(uint8_t* interfaceCount)
{
  *interfaceCount += 1; // uses 1
  HIDDescriptor hidInterface = {
    D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE),
    D_HIDREPORT(sizeof(_hidReportDescriptor)),
    D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01)
  };
  return USB_SendControl(0, &hidInterface, sizeof(hidInterface));
}

int Gamepad_::getDescriptor(USBSetup& setup)
{
  // Check if this is a HID Class Descriptor request
  if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { return 0; }
  if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) { return 0; }

  // In a HID Class Descriptor wIndex cointains the interface number
  if (setup.wIndex != pluggedInterface) { return 0; }

  // Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol
  // due to the USB specs, but Windows and Linux just assumes its in report mode.
  protocol = HID_REPORT_PROTOCOL;

  return USB_SendControl(TRANSFER_PGM, _hidReportDescriptor, sizeof(_hidReportDescriptor));
}

bool Gamepad_::setup(USBSetup& setup)
{
  if (pluggedInterface != setup.wIndex) {
    return false;
  }

  uint8_t request = setup.bRequest;
  uint8_t requestType = setup.bmRequestType;

  if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE)
  {
    if (request == HID_GET_REPORT) {
      // TODO: HID_GetReport();
      return true;
    }
    if (request == HID_GET_PROTOCOL) {
      // TODO: Send8(protocol);
      return true;
    }
  }

  if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE)
  {
    if (request == HID_SET_PROTOCOL) {
      protocol = setup.wValueL;
      return true;
    }
    if (request == HID_SET_IDLE) {
      idle = setup.wValueL;
      return true;
    }
    if (request == HID_SET_REPORT)
    {
    }
  }

  return false;
}

void Gamepad_::reset()
{
  _GamepadReport.X = 0;
  _GamepadReport.Y = 0;
  _GamepadReport.buttons = 0;
  this->send();
}

void Gamepad_::send() 
{
  USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &_GamepadReport, sizeof(GamepadReport));
}

uint8_t Gamepad_::getShortName(char *name)
{
  if(!next) 
  {
    strcpy(name, gp_serial);
    return strlen(name);
  }
  return 0;
}

コレコビジョンコアでNTT DATAキーパッドをつかう

MiSTer FPGAコレコビジョンコアでNTT DATAキーパッドを使いたい! github.com

まずはスーパーファミコンNTT DATAキーパッドをUSB化する基板を選びます。 www.ebay.com

Sparkfun Pro Microが載っているのでファームウェアをカスタマイズできそうです。

デフォルトのファームウェアでは、スーパーファミコンのボタンしか反応しなかったので、以下のものに書き換えました。 github.com

またMicro USBが使いずらいのでUSB CのPro Microに置き換えました。

Pro Micro 5V/16MHz/USB-C(互換品)|TALPKEYBOARD – TALPKEYBOARD SHOP

デフォルトではコントローラーは1つしか認識しないので、2つつなぐために若干修正しました。

/*  DaemonBite (S)NES Controllers to USB Adapter with NTT Datapad support
 *  Author: Mikael Norrgård <mick@daemonbite.com>
 *
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *  
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */

#include "Gamepad.h"

// ATT: 20 chars max (including NULL at the end) according to Arduino source code.
// Additionally serial number is used to differentiate arduino projects to have different button maps!
const char *gp_serial = "NES/SNES to USB";

//#define DEBUG

#define GAMEPAD_COUNT 2      // NOTE: To have more than 2 two gamepads you need to disable the CDC of the Arduino.
#define GAMEPAD_COUNT_MAX 4  
#define BUTTON_READ_DELAY 20 // Delay between button reads in µs
#define CYCLES_LATCH     128 // 12µs according to specs (8 seems to work fine) (1 cycle @ 16MHz takes 62.5ns so 62.5ns * 128 = 8000ns = 8µs)
#define CYCLES_CLOCK      64 //  6µs according to specs (4 seems to work fine)
#define CYCLES_PAUSE      64 //  6µs according to specs (4 seems to work fine)

#define UP    0x01
#define DOWN  0x02
#define LEFT  0x04
#define RIGHT 0x08

#define NTT_CONTROL_BIT 0x20000000

#define DELAY_CYCLES(n) __builtin_avr_delay_cycles(n)

// Wire it all up according to the following table:
//
// NES           SNES        Arduino Pro Micro
// --------------------------------------
// VCC                       VCC (All gamepads)
// GND                       GND (All gamepads)
// OUT0 (LATCH)              2   (PD1, All gamepads)
// CUP  (CLOCK)              3   (PD0, All gamepads)
// D1   (GP1: DATA)          A0  (PF7, Gamepad 1) 
// D1   (GP2: DATA)          A1  (PF6, Gamepad 2)
// D1   (GP3: DATA)          A2  (PF5, Gamepad 3, not currently used)
// D1   (GP4: DATA)          A3  (PF4, Gamepad 4, not currently used)

enum ControllerType {
  NONE,
  NES,
  SNES,
  NTT
};

// Set up USB HID gamepads
Gamepad_ Gamepad[GAMEPAD_COUNT];

// Controllers
uint32_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint32_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000};
ControllerType controllerType[GAMEPAD_COUNT_MAX] = {NONE,NONE,NONE,NONE};
uint32_t btnBits[32] = {0x10,0x40,0x400,0x800,UP,DOWN,LEFT,RIGHT,0x20,0x80,0x100,0x200,          // Standard SNES controller
                        0x10000000,0x20000000,0x40000000,0x80000000,0x1000,0x2000,0x4000,0x8000, // NTT Data Keypad (NDK10)
                        0x10000,0x20000,0x40000,0x80000,0x100000,0x200000,0x400000,0x800000,
                        0x1000000,0x2000000,0x4000000,0x8000000};
uint8_t gp = 0;
uint8_t buttonCount = 32;

// Timing
uint32_t microsButtons = 0;

#ifdef DEBUG
uint32_t microsStart = 0;
uint32_t microsEnd = 0;
uint8_t counter = 0;
#endif

void setup()
{
  // Setup latch and clock pins (2,3 or PD1, PD0)
  DDRD  |=  B00000011; // output
  PORTD &= ~B00000011; // low

  // Setup data pins A0-A3 (PF7-PF4)
  DDRF  &= ~B11110000; // inputs
  PORTF |=  B11110000; // enable internal pull-ups
  DDRC  &= ~B01000000; // input
  PORTC |=  B01000000; // enable internal pull-up

  #ifdef DEBUG
  Serial.begin(115200);
  delay(4000);
  #endif

  delay(500);
  detectControllerTypes();
}

void loop() { while(1)
{
  // See if enough time has passed since last button read
  if((micros() - microsButtons) > BUTTON_READ_DELAY)
  {    

    #ifdef DEBUG
    microsStart = micros();
    #endif
  
    // Pulse latch
    sendLatch();

    for(uint8_t btn=0; btn<buttonCount; btn++)
    {
      for(gp=0; gp<GAMEPAD_COUNT; gp++) 
        (PINF & gpBit[gp]) ? buttons[gp] &= ~btnBits[btn] : buttons[gp] |= btnBits[btn];
      sendClock();
    }

    // Check gamepad type
    for(gp=0; gp<GAMEPAD_COUNT; gp++) 
    {
      if(controllerType[gp] == NES) {    // NES
        bitWrite(buttons[gp], 5, bitRead(buttons[gp], 4));
        bitWrite(buttons[gp], 4, bitRead(buttons[gp], 6));
        buttons[gp] &= 0xC3F;
      }
      else if(controllerType[gp] == NTT) // SNES NTT Data Keypad
        buttons[gp] &= 0x3FFFFFF;
      else                               // SNES Gamepad
        buttons[gp] &= 0xFFF; 
    }

    for(gp=0; gp<GAMEPAD_COUNT; gp++)
    {
      // Has any buttons changed state?
      if (buttons[gp] != buttonsPrev[gp])
      {
        Gamepad[gp]._GamepadReport.buttons = (buttons[gp] >> 4); // First 4 bits are the axes
        Gamepad[gp]._GamepadReport.Y = ((buttons[gp] & DOWN) >> 1) - (buttons[gp] & UP);
        Gamepad[gp]._GamepadReport.X = ((buttons[gp] & RIGHT) >> 3) - ((buttons[gp] & LEFT) >> 2);
        buttonsPrev[gp] = buttons[gp];
        Gamepad[gp].send();
      }
    }
    
    microsButtons = micros();

    #ifdef DEBUG
    microsEnd = micros();
    if(counter < 20) {
      Serial.println(microsEnd-microsStart);
      counter++;
    }
    #endif
    
  }
}}

void detectControllerTypes()
{
  uint8_t buttonCountNew = 0;

  // Read the controllers a few times to detect controller type
  for(uint8_t i=0; i<4; i++) 
  {
    // Pulse latch
    sendLatch();

    // Read all buttons
    for(uint8_t btn=0; btn<buttonCount; btn++)
    {
      for(gp=0; gp<GAMEPAD_COUNT; gp++) 
        (PINF & gpBit[gp]) ? buttons[gp] &= ~btnBits[btn] : buttons[gp] |= btnBits[btn];
      sendClock();
    }

    // Check controller types and set buttonCount to max needed
    for(gp=0; gp<GAMEPAD_COUNT; gp++) 
    {
      if((buttons[gp] & 0xF3A0) == 0xF3A0) {   // NES
        if(controllerType[gp] != SNES && controllerType[gp] != NTT)
          controllerType[gp] = NES;
        if(buttonCountNew < 8)
          buttonCountNew = 8;
      }
      else if(buttons[gp] & NTT_CONTROL_BIT) { // SNES NTT Data Keypad
        controllerType[gp] = NTT;
        buttonCountNew = 32;
      }
      else {                                   // SNES Gamepad
//        if(controllerType[gp] != NTT)
//          controllerType[gp] = SNES;
//        if(buttonCountNew < 12)
//          buttonCountNew = 12;
        controllerType[gp] = NTT;
        buttonCountNew = 32;
      }
    }
  }

  #ifdef DEBUG
  for(gp=0; gp<GAMEPAD_COUNT; gp++) 
  {
    Serial.print("Controller ");
    Serial.print(gp+1);
    Serial.print(": ");
    Serial.println(buttons[gp]);
  }
  #endif

  // Set updated button count to avoid unneccesary button reads (for simpler controller types)
  buttonCount = buttonCountNew;
}

void sendLatch()
{
  // Send a latch pulse to (S)NES controller(s)
  PORTD |=  B00000010; // Set HIGH
  DELAY_CYCLES(CYCLES_LATCH);
  PORTD &= ~B00000010; // Set LOW
  DELAY_CYCLES(CYCLES_PAUSE);
}

void sendClock()
{
  // Send a clock pulse to (S)NES controller(s)
  PORTD |=  B10000001; // Set HIGH
  DELAY_CYCLES(CYCLES_CLOCK); 
  PORTD &= ~B10000001; // Set LOW
  DELAY_CYCLES(CYCLES_PAUSE);
}

電子ゲームで遊ぼう会3(ヨコハマ)

電子ゲームで遊ぶイベントです。

  • FLゲーム
  • LSIゲーム
  • LCDゲーム

ハッシュタグ(2026/9/27)

MAMEVECTOR64その2

DeepSeek、Gemini Notebookによるシリアル通信の改善

// license:BSD-3-Clause
// copyright-holders:Brad Oliver,Aaron Giles,Bernd Wiebelt,Allard van der Bas
/******************************************************************************
 *
 * vector.c
 *
 *        anti-alias code by Andrew Caldwell
 *        (still more to add)
 *
 * 040227 Fixed miny clip scaling which was breaking in mhavoc. AREK
 * 010903 added support for direct RGB modes MLR
 * 980611 use translucent vectors. Thanks to Peter Hirschberg
 *        and Neil Bradley for the inspiration. BW
 * 980307 added cleverer dirty handling. BW, ASG
 *        fixed antialias table .ac
 * 980221 rewrote anti-alias line draw routine
 *        added inline assembly multiply fuction for 8086 based machines
 *        beam diameter added to draw routine
 *        beam diameter is accurate in anti-alias line draw (Tcosin)
 *        flicker added .ac
 * 980203 moved LBO's routines for drawing into a buffer of vertices
 *        from avgdvg.c to this location. Scaling is now initialized
 *        by calling vector_init(...). BW
 * 980202 moved out of msdos.c ASG
 * 980124 added anti-alias line draw routine
 *        modified avgdvg.c and sega.c to support new line draw routine
 *        added two new tables Tinten and Tmerge (for 256 color support)
 *        added find_color routine to build above tables .ac
 *
 **************************************************************************** */

#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"

// Serial port related includes
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <inttypes.h>
#include <sys/time.h>
 

#define FLT_EPSILON 1E-5

#define VECTOR_WIDTH_DENOM 512

#define MAX_POINTS 10000

#define VECTOR_SERIAL_MAX 4095

#define VECTOR_TEAM \
   "-* Vector Heads *-\n" \
   "Brad Oliver\n" \
   "Aaron Giles\n" \
   "Bernd Wiebelt\n" \
   "Allard van der Bas\n" \
   "Al Kossow (VECSIM)\n" \
   "Hedley Rainnie (VECSIM)\n" \
   "Eric Smith (VECSIM)\n" \
   "Neil Bradley (technical advice)\n" \
   "Andrew Caldwell (anti-aliasing)\n" \
   "- *** -\n"

///-----
#ifdef __cplusplus > 201711L
  #define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
  #ifdef __GNUC__
    #define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
  #else
    #define TERMIWIN_MAYBE_UNUSED
  #endif
#endif

#include <fcntl.h>
#include <stdlib.h>

typedef struct COM {
  HANDLE hComm;
  int fd; //Actually it's completely useless
  char port[128];
} COM;

DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure

//LOCAL functions

//nbyte 0->7

int getByte(tcflag_t flag, int nbyte, int nibble) {

  int byte;
  if (nibble == 1)
    byte = (flag >> (8 * (nbyte)) & 0x0f);
  else
    byte = (flag >> (8 * (nbyte)) & 0xf0);
  return byte;
}

//INPUT FUNCTIONS

enum{
  i_IXOFF = 0x01,
  i_IXON = 0x02,
  i_IXOFF_IXON = 0x03,
  i_PARMRK = 0x04,
  i_PARMRK_IXOFF = 0x05,
  i_PARMRK_IXON = 0x06,
  i_PARMRK_IXON_IXOFF = 0x07
};

int getIXOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);

  return byte;
}

//LOCALOPT FUNCTIONS

enum{
  l_NOECHO = 0x00,
  l_ECHO = 0x01,
  l_ECHO_ECHOE = 0x03,
  l_ECHO_ECHOK = 0x05,
  l_ECHO_ECHONL = 0x09,
  l_ECHO_ECHOE_ECHOK = 0x07,
  l_ECHO_ECHOE_ECHONL = 0x0b,
  l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
  l_ECHO_ECHOK_ECHONL = 0x0d,
  l_ECHOE = 0x02,
  l_ECHOE_ECHOK = 0x06,
  l_ECHOE_ECHONL = 0x0a,
  l_ECHOE_ECHOK_ECHONL = 0x0e,
  l_ECHOK = 0x04,
  l_ECHOK_ECHONL = 0x0c,
  l_ECHONL = 0x08
};

int getEchoOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

enum{
  l_ICANON = 0x10,
  l_ICANON_ISIG = 0x50,
  l_ICANON_IEXTEN = 0x30,
  l_ICANON_NOFLSH = 0x90,
  l_ICANON_ISIG_IEXTEN = 0x70,
  l_ICANON_ISIG_NOFLSH = 0xd0,
  l_ICANON_IEXTEN_NOFLSH = 0xb0,
  l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
  l_ISIG = 0x40,
  l_ISIG_IEXTEN = 0x60,
  l_ISIG_NOFLSH = 0xc0,
  l_ISIG_IEXTEN_NOFLSH = 0xe0,
  l_IEXTEN = 0x20,
  l_IEXTEN_NOFLSH = 0xa0,
  l_NOFLSH = 0x80,
};

int getLocalOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

enum{
  l_TOSTOP = 0x01
};

int getToStop(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

//CONTROLOPT FUNCTIONS

int getCharSet(tcflag_t flag) {

  //FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
  int byte = getByte(flag, 1, 1);

  switch (byte) {

  case 0X0:
    return CS5;
    break;

  case 0X4:
    return CS6;
    break;

  case 0X8:
    return CS7;
    break;

  case 0Xc:
    return CS8;
    break;

  default:
    return CS8;
    break;
  }
}

enum{
  c_ALL_ENABLED = 0xd0,
  c_PAREVEN_CSTOPB = 0x50,
  c_PAREVEN_NOCSTOPB = 0x40,
  c_PARODD_NOCSTOPB = 0xc0,
  c_NOPARENB_CSTOPB = 0x10,
  c_ALL_DISABLED = 0x00,
};

int getControlOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

//LIBFUNCTIONS

int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  if (fd != com.fd) return -1;
  int TERMIWIN_MAYBE_UNUSED ret = 0;

  ret = GetCommState(com.hComm, &SerialParams);

  return 0;
}

int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {

  if (fd != com.fd) return -1;
  int ret = 0;

  //Store flags into local variables
  tcflag_t iflag = termios_p->c_iflag;
  tcflag_t lflag = termios_p->c_lflag;
  tcflag_t cflag = termios_p->c_cflag;
  tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;

  //iflag

  int IX = getIXOptions(iflag);

  if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {

    SerialParams.fOutX = TRUE;
    SerialParams.fInX = TRUE;
    SerialParams.fTXContinueOnXoff = TRUE;
  }

  //lflag
  int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);

  //Missing parameters...

  //cflags

  int CharSet = getCharSet(cflag);
  int c_opt = getControlOptions(cflag);

  switch (CharSet) {

  case CS5:
    SerialParams.ByteSize = 5;
    break;

  case CS6:
    SerialParams.ByteSize = 6;
    break;

  case CS7:
    SerialParams.ByteSize = 7;
    break;

  case CS8:
    SerialParams.ByteSize = 8;
    break;
  }

  switch (c_opt) {

  case c_ALL_ENABLED:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_ALL_DISABLED:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PAREVEN_CSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_PAREVEN_NOCSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PARODD_NOCSTOPB:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_NOPARENB_CSTOPB:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;
  }

  //aflags

  /*
  int OP;
  if(oflag == OPOST)
  else ...
  */
  //Missing parameters...

  //special characters

  if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
  if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];

  if (termios_p->c_cc[VMIN] == 1) { //Blocking

    timeouts.ReadIntervalTimeout = 0;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = 0;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = 0;  // in milliseconds
///    timeouts.WriteTotalTimeoutConstant = 0;   // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 1000;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds

  } else { //Non blocking

    timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100;  // in milliseconds
///    timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;   // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 1000;   // in milliseconds
///    timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds
  }

  SetCommTimeouts(com.hComm, &timeouts);

  //EOF

  ret = SetCommState(com.hComm, &SerialParams);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {

  if (fd != com.fd) return -1;

  int ret = 0;
  ret = TransmitCommChar(com.hComm, '\x00');
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcdrain(int fd) {

///  if (fd != com.fd) return -1;
///  return FlushFileBuffers(com.hComm);
    if (fd != com.fd) return -1;

    // FlushFileBuffers(com.hComm) を削除し、即座に成功(0)を返すようにします。
    // これにより、ハードウェア側の送信完了を待たずに次の処理へ進めるようになります。
    return 0; 
}

int tcflush(int fd, int queue_selector) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (queue_selector) {

  case TCIFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    break;

  case TCOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  case TCIOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

int tcflow(int fd, int action) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (action) {

  case TCOOFF:
    rc = PurgeComm(com.hComm, PURGE_TXABORT);
    break;

  case TCOON:
    rc = ClearCommBreak(com.hComm);
    break;

  case TCIOFF:
    rc = PurgeComm(com.hComm, PURGE_RXABORT);
    break;

  case TCION:
    rc = ClearCommBreak(com.hComm);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  SerialParams.ByteSize = 8;
  SerialParams.StopBits = ONESTOPBIT;
  SerialParams.Parity = NOPARITY;
}

speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

ssize_t read_serial(int fd, void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = ReadFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

ssize_t write_serial(int fd, const void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = WriteFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

int open_serial(const char* portname, int opt) {

  if (strlen(portname) < 4) return -1;

  // Set to zero
  memset(com.port, 0x00, 128);

  //COMxx
  size_t portSize = 0;
  if (strlen(portname) > 4) {
    portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
    strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
    strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
  }
  //COMx
  else {
    portSize = sizeof(char) * 5;
  }

#ifdef _MSC_VER
  strncat_s(com.port, portSize, portname, 4);
#else
  strncat(com.port, portname, 4);
#endif
  com.port[portSize] = 0x00;

  switch (opt) {

  case O_RDWR:
    com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_RDONLY:
    com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_WRONLY:
    com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;
  }

  if (com.hComm == INVALID_HANDLE_VALUE) {
    return -1;
  }
  com.fd = atoi(portname + 3); // COMx and COMxx
  SerialParams.DCBlength = sizeof(SerialParams);
  return com.fd;
}

int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {

  int ret = CloseHandle(com.hComm);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {

    DWORD dwErrors;
    COMSTAT cs;

    // 現在のシリアルポートの状態を取得し、エラーをクリアする
    if (!ClearCommError(com.hComm, &dwErrors, &cs)) {
        return -1; // 失敗した場合はエラーを返す
    }

    // 受信バッファにデータ(cbInQue)があるか確認する
    if (cs.cbInQue > 0) {
        return com.fd; // データがあればファイル記述子(ポート番号)を返す
    } else {
        if (readfds) {
            // データがない場合は、呼び出し元のセットから記述子をクリアする
            FD_CLR(com.fd, readfds);
        }
    }

///  SetCommMask(com.hComm, EV_RXCHAR);
///  DWORD dwEventMask;
///  if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
///    return -1; // Return -1 if failed
///  }
///  if (dwEventMask == EV_RXCHAR) {
///    return com.fd;
///  } else {
///    if (readfds) {
      // Clear file descriptor if event is not RXCHAR
///      FD_CLR(com.fd, readfds);
///    }
///  }
  // NOTE: write event not detectable!
  // NOTE: no timeout
  return 0; // No data
}

//Returns hComm from the COM structure
HANDLE getHandle() {
  return com.hComm;
}
///-----

#define VCLEAN  0
#define VDIRTY  1
#define VCLIP   2

// device type definition
const device_type VECTOR = &device_creator<vector_device>;

vector_device::vector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
    : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

vector_device::vector_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, VECTOR, "VECTOR", tag, owner, clock, "vector_device", __FILE__),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

float vector_device::m_flicker = 0.0f;
float vector_device::m_beam_width_min = 0.0f;
float vector_device::m_beam_width_max = 0.0f;
float vector_device::m_beam_intensity_weight = 0.0f;
int vector_device::m_vector_index;

struct serial_segment_t {
    struct serial_segment_t * next;
    int intensity;
    int x0;
    int y0;
    int x1;
    int y1;

    serial_segment_t(
        int x0,
        int y0,
        int x1,
        int y1,
        int intensity
    ) :
        next(NULL),
        intensity(intensity),
        x0(x0),
        y0(y0),
        x1(x1),
        y1(y1)
    {
    }
};

int
serial_open(
        const char * const dev
)
{
///        const int fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY, 0666);
        const int fd = open(dev, O_RDWR, 0666);
        if (fd < 0)
                return -1;

        // Disable modem control signals
        struct termios attr;
        tcgetattr(fd, &attr);
        attr.c_cflag |= CLOCAL | CREAD;
        attr.c_oflag &= ~OPOST;
        tcsetattr(fd, TCSANOW, &attr);

        return fd;
}

void vector_device::serial_draw_point(
    unsigned x,
    unsigned y,
    int intensity
)
{
    // make sure that we are in range; should always be
    // due to clipping on the window, but just in case
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    if (x > VECTOR_SERIAL_MAX) x = VECTOR_SERIAL_MAX;
    if (y > VECTOR_SERIAL_MAX) y = VECTOR_SERIAL_MAX;

    // always flip the Y, since the vectorscope measures
    // 0,0 at the bottom left corner, but this coord uses
    // the top left corner.
    y = VECTOR_SERIAL_MAX - y;

    unsigned bright;
    if (intensity > m_serial_bright)
        bright = 63;
    else
    if (intensity <= 0)
        bright = 0;
    else
        bright = (intensity * 64) / 256;

    if (bright > 63)
        bright = 63;

    if (m_serial_rotate == 1)
    {
        // +90
        unsigned tmp = x;
        x = VECTOR_SERIAL_MAX - y;
        y = tmp;
    } else
    if (m_serial_rotate == 2)
    {
        // +180
        x = VECTOR_SERIAL_MAX - x;
        y = VECTOR_SERIAL_MAX - y;
    } else
    if (m_serial_rotate == 3)
    {
        // -90
        unsigned t = x;
        x = y;
        y = VECTOR_SERIAL_MAX - t;
    }

    uint32_t cmd = 0
        | (2 << 30)
        | (bright & 0x3F) << 24
        | (x & 0xFFF) << 12
        | (y & 0xFFF) <<  0
        ;

    //printf("%08x %8d %8d %3d\n", cmd, x, y, intensity);

    m_serial_buf[m_serial_offset++] = cmd >> 24;
    m_serial_buf[m_serial_offset++] = cmd >> 16;
    m_serial_buf[m_serial_offset++] = cmd >>  8;
    m_serial_buf[m_serial_offset++] = cmd >>  0;

    // todo: check for overflow;
    // should always have enough points
}


// This will only be called with non-zero intensity lines.
// we keep a linked list of the vectors and sort them with
// a greedy insertion sort.
void vector_device::serial_draw_line(
    float xf0,
    float yf0,
    float xf1,
    float yf1,
    int intensity
)
{
    if (m_serial_fd < 0)
        return;

    // scale and shift each of the axes.
    const int x0 = (xf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y0 = (yf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;
    const int x1 = (xf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y1 = (yf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;

    serial_segment_t * const new_segment
        = new serial_segment_t(x0, y0, x1, y1, intensity);

    if (this->m_serial_segments_tail)
        this->m_serial_segments_tail->next = new_segment;
    else
        this->m_serial_segments = new_segment;

    this->m_serial_segments_tail = new_segment;
}


void vector_device::serial_reset()
{
    m_serial_offset = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;

    m_vector_transit[0] = 0;
    m_vector_transit[1] = 0;
    m_vector_transit[2] = 0;
}


void vector_device::serial_send()
{
    if (m_serial_fd < 0)
        return;

    int last_x = -1;
    int last_y = -1;

    // find the next closest point to the last one.
    // greedy sorting algorithm reduces beam transit time
    // fairly significantly. doesn't matter for the
    // vectorscope, but makes a big difference for Vectrex
    // and other slower displays.
    while(this->m_serial_segments)
    {
        int reverse = 0;
        int min = 1e6;
        serial_segment_t ** min_seg
            = &this->m_serial_segments;

        if (m_serial_sort)
        for(serial_segment_t ** s = min_seg ; *s ; s = &(*s)->next)
        {
            int dx0 = (*s)->x0 - last_x;
            int dy0 = (*s)->y0 - last_y;
            int dx1 = (*s)->x1 - last_x;
            int dy1 = (*s)->y1 - last_y;
            int d0 = sqrt(dx0*dx0 + dy0*dy0);
            int d1 = sqrt(dx1*dx1 + dy1*dy1);

            if(d0 < min)
            {
                min_seg = s;
                min = d0;
                reverse = 0;
            }

            if (d1 < min)
            {
                min_seg = s;
                min = d1;
                reverse = 1;
            }

            // if we have hit two identical points,
            // then stop the search here.
            if (min == 0)
                break;
        }

        serial_segment_t * const s = *min_seg;
        if (!s)
            break;
    
        const int x0 = reverse ? s->x1 : s->x0;
        const int y0 = reverse ? s->y1 : s->y0;
        const int x1 = reverse ? s->x0 : s->x1;
        const int y1 = reverse ? s->y0 : s->y1;

        // if this is not a continuous segment,
        // we must add a transit command
        if (last_x != x0 || last_y != y0)
        {
            serial_draw_point(x0, y0, 0);
            int dx = x0 - last_x;
            int dy = y0 - last_y;
            m_vector_transit[0] += sqrt(dx*dx + dy*dy);
        }

        // transit to the new point
        int dx = x1 - x0;
        int dy = y1 - y0;
        int dist = sqrt(dx*dx + dy*dy);

        serial_draw_point(x1, y1, s->intensity);
        last_x = x1;
        last_y = y1;

        if (s->intensity > m_serial_bright)
            m_vector_transit[2] += dist;
        else
            m_vector_transit[1] += dist;

        // delete this segment from the list
        *min_seg = s->next;
        delete s;
    }

    // ensure that we erase our tracks
    if(this->m_serial_segments != NULL)
        fprintf(stderr, "errr?\n");
    this->m_serial_segments = NULL;
    this->m_serial_segments_tail = NULL;

    // add the "done" command to the message
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;

    size_t offset = 0;

///    if(1)
///    printf("%zu vectors: off=%u on=%u bright=%u%s\n",
///        m_serial_offset/4,
///        m_vector_transit[0],
///        m_vector_transit[1],
///        m_vector_transit[2],
///        m_serial_drop_frame ? " !" : ""
///    );

    static unsigned skip_frame;
    unsigned eagain = 0;

    if (m_serial_drop_frame || skip_frame++ % 2 != 0)
    {
        // we skipped a frame, don't skip the next one
        m_serial_drop_frame = 0;
    } else
    while (offset < m_serial_offset)
    {
        size_t wlen = m_serial_offset - offset;
///        if (wlen > 64)
///            wlen = 64;
        if (wlen > 4096)
            wlen = 4096;

///        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, m_serial_offset - offset);
        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, wlen);
///        if (rc <= 0)
///        {
///            eagain++;
///            if (errno == EAGAIN)
///                continue;
///            perror(m_serial);
///            close(m_serial_fd);
///            m_serial_fd = -1;
///            break;
///        }
        if (rc <= 0)
        {
            if (rc == 0) {
                eagain++;
///                Sleep(1);
                continue;
            }
            // Real error: close and bail
            perror(m_serial);
            close(m_serial_fd);
            m_serial_fd = -1;
            break;
        }

        offset += rc;
    }

///    printf("%d eagain.\n", eagain);
///    if (eagain > 20)
    if (eagain > 5)
        m_serial_drop_frame = 1;

    serial_reset();
}



void vector_device::device_start()
{
    /* Grab the settings for this session */
    m_beam_width_min = machine().options().beam_width_min();
    m_beam_width_max = machine().options().beam_width_max();
    m_beam_intensity_weight = machine().options().beam_intensity_weight();
    m_flicker = machine().options().flicker();

    m_vector_index = 0;

    /* allocate memory for tables */
    m_vector_list = make_unique_clear<point[]>(MAX_POINTS);

    /* Setup the serial output of the XY coords if configured */
    m_serial = machine().options().vector_serial();
    const float scale = machine().options().vector_scale();
    if (scale != 0.0)
    {
        // user specified a scale on the command line
        m_serial_scale_x = m_serial_scale_y = scale;
    } else {
        // use the per-axis scales
        m_serial_scale_x = machine().options().vector_scale_x();
        m_serial_scale_y = machine().options().vector_scale_y();
    }

    m_serial_segments = m_serial_segments_tail = NULL;

    m_serial_offset_x = machine().options().vector_offset_x();
    m_serial_offset_y = machine().options().vector_offset_y();
    m_serial_rotate = machine().options().vector_rotate();
    m_serial_bright = machine().options().vector_bright();
    m_serial_drop_frame = 0;
    m_serial_sort = 1;

    // allocate enough buffer space, although we should never use this much
    m_serial_buf = auto_alloc_array_clear(machine(), unsigned char, (MAX_POINTS+2) * 4);
    if (!m_serial_buf)
    {
        // todo: how to signal an error?
    }

    serial_reset();

    if (!m_serial || strcmp(m_serial,"") == 0)
    {
        fprintf(stderr, "no serial vector display configured\n");
        m_serial_fd = -1;
    } else {
        m_serial_fd = serial_open(m_serial);
        fprintf(stderr, "serial dev='%s' fd=%d\n", m_serial, m_serial_fd);
    }
}

void vector_device::set_flicker(float newval)
{
    m_flicker = newval;
}

float vector_device::get_flicker()
{
    return m_flicker;
}

void vector_device::set_beam_width_min(float newval)
{
    m_beam_width_min = newval;
}

float vector_device::get_beam_width_min()
{
    return m_beam_width_min;
}

void vector_device::set_beam_width_max(float newval)
{
    m_beam_width_max = newval;
}

float vector_device::get_beam_width_max()
{
    return m_beam_width_max;
}

void vector_device::set_beam_intensity_weight(float newval)
{
    m_beam_intensity_weight = newval;
}

float vector_device::get_beam_intensity_weight()
{
    return m_beam_intensity_weight;
}


/*
 * www.dinodini.wordpress.com/2010/04/05/normalized-tunable-sigmoid-functions/
 */
float vector_device::normalized_sigmoid(float n, float k)
{
    // valid for n and k in range of -1.0 and 1.0
    return (n - n * k) / (k - fabs(n) * 2.0f * k + 1.0f);
}


/*
 * Adds a line end point to the vertices list. The vector processor emulation
 * needs to call this.
 */
void vector_device::add_point(int x, int y, rgb_t color, int intensity)
{
    point *newpoint;

//printf("%d %d: %d,%d,%d @ %d\n", x, y, color.r(), color.b(), color.g(), intensity);

    // hack for the vectrex
    // -- convert "128,128,128" @ 255 to "255,255,255" @ 127
    if (color.r() == 128
    &&  color.b() == 128
    &&  color.g() == 128
    &&  intensity == 255)
    {
        color = rgb_t(255,255,255);
        intensity = 128;
    }

    intensity = MAX(0, MIN(255, intensity));

    m_min_intensity = intensity > 0 ? MIN(m_min_intensity, intensity) : m_min_intensity;
    m_max_intensity = intensity > 0 ? MAX(m_max_intensity, intensity) : m_max_intensity;

    if (m_flicker && (intensity > 0))
    {
        float random = (float)(machine().rand() & 255) / 255.0f; // random value between 0.0 and 1.0

        intensity -= (int)(intensity * random * m_flicker);

        intensity = MAX(0, MIN(255, intensity));
    }

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x;
    newpoint->y = y;
    newpoint->col = color;
    newpoint->intensity = intensity;
    newpoint->status = VDIRTY; /* mark identical lines as clean later */

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * Add new clipping info to the list
 */
void vector_device::add_clip(int x1, int yy1, int x2, int y2)
{
    point *newpoint;

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x1;
    newpoint->y = yy1;
    newpoint->arg1 = x2;
    newpoint->arg2 = y2;
    newpoint->status = VCLIP;

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * The vector CPU creates a new display list. We save the old display list,
 * but only once per refresh.
 */
void vector_device::clear_list(void)
{
    m_vector_index = 0;
}


UINT32 vector_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
    UINT32 flags = PRIMFLAG_ANTIALIAS(screen.machine().options().antialias() ? 1 : 0) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD) | PRIMFLAG_VECTOR(1);
    const rectangle &visarea = screen.visible_area();
    float xscale = 1.0f / (65536 * visarea.width());
    float yscale = 1.0f / (65536 * visarea.height());
    float xoffs = (float)visarea.min_x;
    float yoffs = (float)visarea.min_y;
    float xratio = xscale / yscale;
    float yratio = yscale / xscale;
    xratio = (xratio < 1.0f) ? xratio : 1.0f;
    yratio = (yratio < 1.0f) ? yratio : 1.0f;

    point *curpoint;
    render_bounds clip;
    int lastx = 0;
    int lasty = 0;

    curpoint = m_vector_list.get();

    screen.container().empty();
    screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1));

    clip.x0 = clip.y0 = 0.0f;
    clip.x1 = clip.y1 = 1.0f;

    for (int i = 0; i < m_vector_index; i++)
    {
        render_bounds coords;

        if (curpoint->status == VCLIP)
        {
            coords.x0 = ((float)curpoint->x - xoffs) * xscale;
            coords.y0 = ((float)curpoint->y - yoffs) * yscale;
            coords.x1 = ((float)curpoint->arg1 - xoffs) * xscale;
            coords.y1 = ((float)curpoint->arg2 - yoffs) * yscale;

            clip.x0 = (coords.x0 > 0.0f) ? coords.x0 : 0.0f;
            clip.y0 = (coords.y0 > 0.0f) ? coords.y0 : 0.0f;
            clip.x1 = (coords.x1 < 1.0f) ? coords.x1 : 1.0f;
            clip.y1 = (coords.y1 < 1.0f) ? coords.y1 : 1.0f;
        }
        else
        {
            float beam_intensity_width = m_beam_width_min;

            float intensity = (float)curpoint->intensity / 255.0f;

            // check for dynamic intensity
            if (m_min_intensity != m_max_intensity)
            {
                float intensity_weight = normalized_sigmoid(intensity, m_beam_intensity_weight);
                beam_intensity_width = (m_beam_width_max - m_beam_width_min) * intensity_weight + m_beam_width_min;
            }

            float beam_width = beam_intensity_width * (1.0f / (float)VECTOR_WIDTH_DENOM);

            coords.x0 = ((float)lastx - xoffs) * xscale;
            coords.y0 = ((float)lasty - yoffs) * yscale;
            coords.x1 = ((float)curpoint->x - xoffs) * xscale;
            coords.y1 = ((float)curpoint->y - yoffs) * yscale;

            // extend zero-length vector line (vector point) by quarter beam_width on both sides
            if (fabs(coords.x0 - coords.x1) < FLT_EPSILON &&
                fabs(coords.y0 - coords.y1) < FLT_EPSILON)
            {
                coords.x0 += xratio * beam_width * 0.25f;
                coords.y0 += yratio * beam_width * 0.25f;
                coords.x1 -= xratio * beam_width * 0.25f;
                coords.y1 -= yratio * beam_width * 0.25f;
            }

            if (curpoint->intensity != 0 && !render_clip_line(&coords, &clip))
            {
                screen.container().add_line(
                    coords.x0, coords.y0, coords.x1, coords.y1,
                    beam_width,
                    (curpoint->intensity << 24) | (curpoint->col & 0xffffff),
                    flags);

                serial_draw_line(
                    coords.x0, coords.y0,
                    coords.x1, coords.y1,
                    curpoint->intensity);
            }

            lastx = curpoint->x;
            lasty = curpoint->y;
        }

        curpoint++;
    }

    serial_send();

    return 0;
}

MAMEVECTOR64その1

はじめにMSYS2 MinGWをインストールして、Windowsで最新版のMAMEがビルドできることを確認します。

www.msys2.org

gist.github.com

つぎにLinuxのMAMEVECTOR64をWindowsでビルドします。

trmm.net

termios.hについてはこちらをベースにしました。

github.com

エラーが出たら修正を繰り返してビルドできました。

$ git clone https://github.com/veeso/termiWin
$ cp termiWin/include/termi*.h /mingw64/include/
$ git clone https://github.com/osresearch/mame/
$ cd mame
$ export MINGW64=/mingw64
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/libformats.a build/mingw-gcc/obj/x64/Release/src/lib/formats/*.o
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/cpu -name "*.o")
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/libbus.a build/mingw-gcc/obj/x64/Release/src/devices/bus/generic/*.o
$ make NOWERROR=1 SUBTARGET=vector
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/machine -name "*.o")
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/liboptional.a $(find build/mingw-gcc/obj/x64/Release/src/devices/sound -name "*.o")
$ ar rcs build/mingw-gcc/bin/x64/Release/mame_vector/libbus.a $(find build/mingw-gcc/obj/x64/Release/src/devices/bus/vectrex -name "*.o")
$ make NOWERROR=1 SUBTARGET=vector

以下のファイルを修正しました。

scripts/build/verinfo.py(抜粋)

try:
#    fp = open(srcfile, 'rU')
    fp = open(srcfile, 'r')
except IOError:
    sys.stderr.write("Unable to open source file '%s'\n" % srcfile)
    sys.exit(1)

src/devices/cpu/m6502/m6502make.py(抜粋)

    try:
#        f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read opcodes file %s [%s]", fname, err)
        sys.exit(1)

(省略)

    try:
#        f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        logging.error("cannot read display file %s [%s]", fname, err)
        sys.exit(1)

src/devices/cpu/m6809/m6809make.py(抜粋)

   try:
#      f = open(fname, "rU")
        f = open(fname, "r")
    except Exception:
        err = sys.exc_info()[1]
        sys.stderr.write("Cannot read opcodes file %s [%s]\n" % (fname, err))
        sys.exit(1)    

src/emu/video/vector.cpp

// license:BSD-3-Clause
// copyright-holders:Brad Oliver,Aaron Giles,Bernd Wiebelt,Allard van der Bas
/******************************************************************************
 *
 * vector.c
 *
 *        anti-alias code by Andrew Caldwell
 *        (still more to add)
 *
 * 040227 Fixed miny clip scaling which was breaking in mhavoc. AREK
 * 010903 added support for direct RGB modes MLR
 * 980611 use translucent vectors. Thanks to Peter Hirschberg
 *        and Neil Bradley for the inspiration. BW
 * 980307 added cleverer dirty handling. BW, ASG
 *        fixed antialias table .ac
 * 980221 rewrote anti-alias line draw routine
 *        added inline assembly multiply fuction for 8086 based machines
 *        beam diameter added to draw routine
 *        beam diameter is accurate in anti-alias line draw (Tcosin)
 *        flicker added .ac
 * 980203 moved LBO's routines for drawing into a buffer of vertices
 *        from avgdvg.c to this location. Scaling is now initialized
 *        by calling vector_init(...). BW
 * 980202 moved out of msdos.c ASG
 * 980124 added anti-alias line draw routine
 *        modified avgdvg.c and sega.c to support new line draw routine
 *        added two new tables Tinten and Tmerge (for 256 color support)
 *        added find_color routine to build above tables .ac
 *
 **************************************************************************** */

#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"

// Serial port related includes
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <inttypes.h>
#include <sys/time.h>
 

#define FLT_EPSILON 1E-5

#define VECTOR_WIDTH_DENOM 512

#define MAX_POINTS 10000

#define VECTOR_SERIAL_MAX 4095

#define VECTOR_TEAM \
   "-* Vector Heads *-\n" \
   "Brad Oliver\n" \
   "Aaron Giles\n" \
   "Bernd Wiebelt\n" \
   "Allard van der Bas\n" \
   "Al Kossow (VECSIM)\n" \
   "Hedley Rainnie (VECSIM)\n" \
   "Eric Smith (VECSIM)\n" \
   "Neil Bradley (technical advice)\n" \
   "Andrew Caldwell (anti-aliasing)\n" \
   "- *** -\n"

///-----
#ifdef __cplusplus > 201711L
  #define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
  #ifdef __GNUC__
    #define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
  #else
    #define TERMIWIN_MAYBE_UNUSED
  #endif
#endif

#include <fcntl.h>
#include <stdlib.h>

typedef struct COM {
  HANDLE hComm;
  int fd; //Actually it's completely useless
  char port[128];
} COM;

DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure

//LOCAL functions

//nbyte 0->7

int getByte(tcflag_t flag, int nbyte, int nibble) {

  int byte;
  if (nibble == 1)
    byte = (flag >> (8 * (nbyte)) & 0x0f);
  else
    byte = (flag >> (8 * (nbyte)) & 0xf0);
  return byte;
}

//INPUT FUNCTIONS

enum{
  i_IXOFF = 0x01,
  i_IXON = 0x02,
  i_IXOFF_IXON = 0x03,
  i_PARMRK = 0x04,
  i_PARMRK_IXOFF = 0x05,
  i_PARMRK_IXON = 0x06,
  i_PARMRK_IXON_IXOFF = 0x07
};

int getIXOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);

  return byte;
}

//LOCALOPT FUNCTIONS

enum{
  l_NOECHO = 0x00,
  l_ECHO = 0x01,
  l_ECHO_ECHOE = 0x03,
  l_ECHO_ECHOK = 0x05,
  l_ECHO_ECHONL = 0x09,
  l_ECHO_ECHOE_ECHOK = 0x07,
  l_ECHO_ECHOE_ECHONL = 0x0b,
  l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
  l_ECHO_ECHOK_ECHONL = 0x0d,
  l_ECHOE = 0x02,
  l_ECHOE_ECHOK = 0x06,
  l_ECHOE_ECHONL = 0x0a,
  l_ECHOE_ECHOK_ECHONL = 0x0e,
  l_ECHOK = 0x04,
  l_ECHOK_ECHONL = 0x0c,
  l_ECHONL = 0x08
};

int getEchoOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

enum{
  l_ICANON = 0x10,
  l_ICANON_ISIG = 0x50,
  l_ICANON_IEXTEN = 0x30,
  l_ICANON_NOFLSH = 0x90,
  l_ICANON_ISIG_IEXTEN = 0x70,
  l_ICANON_ISIG_NOFLSH = 0xd0,
  l_ICANON_IEXTEN_NOFLSH = 0xb0,
  l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
  l_ISIG = 0x40,
  l_ISIG_IEXTEN = 0x60,
  l_ISIG_NOFLSH = 0xc0,
  l_ISIG_IEXTEN_NOFLSH = 0xe0,
  l_IEXTEN = 0x20,
  l_IEXTEN_NOFLSH = 0xa0,
  l_NOFLSH = 0x80,
};

int getLocalOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

enum{
  l_TOSTOP = 0x01
};

int getToStop(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

//CONTROLOPT FUNCTIONS

int getCharSet(tcflag_t flag) {

  //FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
  int byte = getByte(flag, 1, 1);

  switch (byte) {

  case 0X0:
    return CS5;
    break;

  case 0X4:
    return CS6;
    break;

  case 0X8:
    return CS7;
    break;

  case 0Xc:
    return CS8;
    break;

  default:
    return CS8;
    break;
  }
}

enum{
  c_ALL_ENABLED = 0xd0,
  c_PAREVEN_CSTOPB = 0x50,
  c_PAREVEN_NOCSTOPB = 0x40,
  c_PARODD_NOCSTOPB = 0xc0,
  c_NOPARENB_CSTOPB = 0x10,
  c_ALL_DISABLED = 0x00,
};

int getControlOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

//LIBFUNCTIONS

int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  if (fd != com.fd) return -1;
  int TERMIWIN_MAYBE_UNUSED ret = 0;

  ret = GetCommState(com.hComm, &SerialParams);

  return 0;
}

int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {

  if (fd != com.fd) return -1;
  int ret = 0;

  //Store flags into local variables
  tcflag_t iflag = termios_p->c_iflag;
  tcflag_t lflag = termios_p->c_lflag;
  tcflag_t cflag = termios_p->c_cflag;
  tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;

  //iflag

  int IX = getIXOptions(iflag);

  if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {

    SerialParams.fOutX = TRUE;
    SerialParams.fInX = TRUE;
    SerialParams.fTXContinueOnXoff = TRUE;
  }

  //lflag
  int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);

  //Missing parameters...

  //cflags

  int CharSet = getCharSet(cflag);
  int c_opt = getControlOptions(cflag);

  switch (CharSet) {

  case CS5:
    SerialParams.ByteSize = 5;
    break;

  case CS6:
    SerialParams.ByteSize = 6;
    break;

  case CS7:
    SerialParams.ByteSize = 7;
    break;

  case CS8:
    SerialParams.ByteSize = 8;
    break;
  }

  switch (c_opt) {

  case c_ALL_ENABLED:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_ALL_DISABLED:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PAREVEN_CSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_PAREVEN_NOCSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PARODD_NOCSTOPB:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_NOPARENB_CSTOPB:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;
  }

  //aflags

  /*
  int OP;
  if(oflag == OPOST)
  else ...
  */
  //Missing parameters...

  //special characters

  if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
  if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];

  if (termios_p->c_cc[VMIN] == 1) { //Blocking

    timeouts.ReadIntervalTimeout = 0;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = 0;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = 0;  // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 0;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds

  } else { //Non blocking

    timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100;  // in milliseconds
    timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
  }

  SetCommTimeouts(com.hComm, &timeouts);

  //EOF

  ret = SetCommState(com.hComm, &SerialParams);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {

  if (fd != com.fd) return -1;

  int ret = 0;
  ret = TransmitCommChar(com.hComm, '\x00');
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcdrain(int fd) {

  if (fd != com.fd) return -1;
  return FlushFileBuffers(com.hComm);
}

int tcflush(int fd, int queue_selector) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (queue_selector) {

  case TCIFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    break;

  case TCOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  case TCIOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

int tcflow(int fd, int action) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (action) {

  case TCOOFF:
    rc = PurgeComm(com.hComm, PURGE_TXABORT);
    break;

  case TCOON:
    rc = ClearCommBreak(com.hComm);
    break;

  case TCIOFF:
    rc = PurgeComm(com.hComm, PURGE_RXABORT);
    break;

  case TCION:
    rc = ClearCommBreak(com.hComm);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  SerialParams.ByteSize = 8;
  SerialParams.StopBits = ONESTOPBIT;
  SerialParams.Parity = NOPARITY;
}

speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

ssize_t read_serial(int fd, void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = ReadFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

ssize_t write_serial(int fd, const void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = WriteFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

int open_serial(const char* portname, int opt) {

  if (strlen(portname) < 4) return -1;

  // Set to zero
  memset(com.port, 0x00, 128);

  //COMxx
  size_t portSize = 0;
  if (strlen(portname) > 4) {
    portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
    strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
    strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
  }
  //COMx
  else {
    portSize = sizeof(char) * 5;
  }

#ifdef _MSC_VER
  strncat_s(com.port, portSize, portname, 4);
#else
  strncat(com.port, portname, 4);
#endif
  com.port[portSize] = 0x00;

  switch (opt) {

  case O_RDWR:
    com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_RDONLY:
    com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_WRONLY:
    com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;
  }

  if (com.hComm == INVALID_HANDLE_VALUE) {
    return -1;
  }
  com.fd = atoi(portname + 3); // COMx and COMxx
  SerialParams.DCBlength = sizeof(SerialParams);
  return com.fd;
}

int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {

  int ret = CloseHandle(com.hComm);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {

  SetCommMask(com.hComm, EV_RXCHAR);
  DWORD dwEventMask;
  if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
    return -1; // Return -1 if failed
  }
  if (dwEventMask == EV_RXCHAR) {
    return com.fd;
  } else {
    if (readfds) {
      // Clear file descriptor if event is not RXCHAR
      FD_CLR(com.fd, readfds);
    }
  }
  // NOTE: write event not detectable!
  // NOTE: no timeout
  return 0; // No data
}

//Returns hComm from the COM structure
HANDLE getHandle() {
  return com.hComm;
}
///-----

#define VCLEAN  0
#define VDIRTY  1
#define VCLIP   2

// device type definition
const device_type VECTOR = &device_creator<vector_device>;

vector_device::vector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
    : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

vector_device::vector_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, VECTOR, "VECTOR", tag, owner, clock, "vector_device", __FILE__),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

float vector_device::m_flicker = 0.0f;
float vector_device::m_beam_width_min = 0.0f;
float vector_device::m_beam_width_max = 0.0f;
float vector_device::m_beam_intensity_weight = 0.0f;
int vector_device::m_vector_index;

struct serial_segment_t {
    struct serial_segment_t * next;
    int intensity;
    int x0;
    int y0;
    int x1;
    int y1;

    serial_segment_t(
        int x0,
        int y0,
        int x1,
        int y1,
        int intensity
    ) :
        next(NULL),
        intensity(intensity),
        x0(x0),
        y0(y0),
        x1(x1),
        y1(y1)
    {
    }
};

int
serial_open(
        const char * const dev
)
{
///        const int fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY, 0666);
        const int fd = open(dev, O_RDWR, 0666);
        if (fd < 0)
                return -1;

        // Disable modem control signals
        struct termios attr;
        tcgetattr(fd, &attr);
        attr.c_cflag |= CLOCAL | CREAD;
        attr.c_oflag &= ~OPOST;
        tcsetattr(fd, TCSANOW, &attr);

        return fd;
}

void vector_device::serial_draw_point(
    unsigned x,
    unsigned y,
    int intensity
)
{
    // make sure that we are in range; should always be
    // due to clipping on the window, but just in case
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    if (x > VECTOR_SERIAL_MAX) x = VECTOR_SERIAL_MAX;
    if (y > VECTOR_SERIAL_MAX) y = VECTOR_SERIAL_MAX;

    // always flip the Y, since the vectorscope measures
    // 0,0 at the bottom left corner, but this coord uses
    // the top left corner.
    y = VECTOR_SERIAL_MAX - y;

    unsigned bright;
    if (intensity > m_serial_bright)
        bright = 63;
    else
    if (intensity <= 0)
        bright = 0;
    else
        bright = (intensity * 64) / 256;

    if (bright > 63)
        bright = 63;

    if (m_serial_rotate == 1)
    {
        // +90
        unsigned tmp = x;
        x = VECTOR_SERIAL_MAX - y;
        y = tmp;
    } else
    if (m_serial_rotate == 2)
    {
        // +180
        x = VECTOR_SERIAL_MAX - x;
        y = VECTOR_SERIAL_MAX - y;
    } else
    if (m_serial_rotate == 3)
    {
        // -90
        unsigned t = x;
        x = y;
        y = VECTOR_SERIAL_MAX - t;
    }

    uint32_t cmd = 0
        | (2 << 30)
        | (bright & 0x3F) << 24
        | (x & 0xFFF) << 12
        | (y & 0xFFF) <<  0
        ;

    //printf("%08x %8d %8d %3d\n", cmd, x, y, intensity);

    m_serial_buf[m_serial_offset++] = cmd >> 24;
    m_serial_buf[m_serial_offset++] = cmd >> 16;
    m_serial_buf[m_serial_offset++] = cmd >>  8;
    m_serial_buf[m_serial_offset++] = cmd >>  0;

    // todo: check for overflow;
    // should always have enough points
}


// This will only be called with non-zero intensity lines.
// we keep a linked list of the vectors and sort them with
// a greedy insertion sort.
void vector_device::serial_draw_line(
    float xf0,
    float yf0,
    float xf1,
    float yf1,
    int intensity
)
{
    if (m_serial_fd < 0)
        return;

    // scale and shift each of the axes.
    const int x0 = (xf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y0 = (yf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;
    const int x1 = (xf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y1 = (yf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;

    serial_segment_t * const new_segment
        = new serial_segment_t(x0, y0, x1, y1, intensity);

    if (this->m_serial_segments_tail)
        this->m_serial_segments_tail->next = new_segment;
    else
        this->m_serial_segments = new_segment;

    this->m_serial_segments_tail = new_segment;
}


void vector_device::serial_reset()
{
    m_serial_offset = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;

    m_vector_transit[0] = 0;
    m_vector_transit[1] = 0;
    m_vector_transit[2] = 0;
}


void vector_device::serial_send()
{
    if (m_serial_fd < 0)
        return;

    int last_x = -1;
    int last_y = -1;

    // find the next closest point to the last one.
    // greedy sorting algorithm reduces beam transit time
    // fairly significantly. doesn't matter for the
    // vectorscope, but makes a big difference for Vectrex
    // and other slower displays.
    while(this->m_serial_segments)
    {
        int reverse = 0;
        int min = 1e6;
        serial_segment_t ** min_seg
            = &this->m_serial_segments;

        if (m_serial_sort)
        for(serial_segment_t ** s = min_seg ; *s ; s = &(*s)->next)
        {
            int dx0 = (*s)->x0 - last_x;
            int dy0 = (*s)->y0 - last_y;
            int dx1 = (*s)->x1 - last_x;
            int dy1 = (*s)->y1 - last_y;
            int d0 = sqrt(dx0*dx0 + dy0*dy0);
            int d1 = sqrt(dx1*dx1 + dy1*dy1);

            if(d0 < min)
            {
                min_seg = s;
                min = d0;
                reverse = 0;
            }

            if (d1 < min)
            {
                min_seg = s;
                min = d1;
                reverse = 1;
            }

            // if we have hit two identical points,
            // then stop the search here.
            if (min == 0)
                break;
        }

        serial_segment_t * const s = *min_seg;
        if (!s)
            break;
    
        const int x0 = reverse ? s->x1 : s->x0;
        const int y0 = reverse ? s->y1 : s->y0;
        const int x1 = reverse ? s->x0 : s->x1;
        const int y1 = reverse ? s->y0 : s->y1;

        // if this is not a continuous segment,
        // we must add a transit command
        if (last_x != x0 || last_y != y0)
        {
            serial_draw_point(x0, y0, 0);
            int dx = x0 - last_x;
            int dy = y0 - last_y;
            m_vector_transit[0] += sqrt(dx*dx + dy*dy);
        }

        // transit to the new point
        int dx = x1 - x0;
        int dy = y1 - y0;
        int dist = sqrt(dx*dx + dy*dy);

        serial_draw_point(x1, y1, s->intensity);
        last_x = x1;
        last_y = y1;

        if (s->intensity > m_serial_bright)
            m_vector_transit[2] += dist;
        else
            m_vector_transit[1] += dist;

        // delete this segment from the list
        *min_seg = s->next;
        delete s;
    }

    // ensure that we erase our tracks
    if(this->m_serial_segments != NULL)
        fprintf(stderr, "errr?\n");
    this->m_serial_segments = NULL;
    this->m_serial_segments_tail = NULL;

    // add the "done" command to the message
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;

    size_t offset = 0;

    if(1)
    printf("%zu vectors: off=%u on=%u bright=%u%s\n",
        m_serial_offset/4,
        m_vector_transit[0],
        m_vector_transit[1],
        m_vector_transit[2],
        m_serial_drop_frame ? " !" : ""
    );

    static unsigned skip_frame;
    unsigned eagain = 0;

    if (m_serial_drop_frame || skip_frame++ % 2 != 0)
    {
        // we skipped a frame, don't skip the next one
        m_serial_drop_frame = 0;
    } else
    while (offset < m_serial_offset)
    {
        size_t wlen = m_serial_offset - offset;
///        if (wlen > 64)
///            wlen = 64;
        if (wlen > 4096)
            wlen = 4096;

        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, m_serial_offset - offset);
        if (rc <= 0)
        {
            eagain++;
            if (errno == EAGAIN)
                continue;
            perror(m_serial);
            close(m_serial_fd);
            m_serial_fd = -1;
            break;
        }

        offset += rc;
    }

    printf("%d eagain.\n", eagain);
    if (eagain > 20)
        m_serial_drop_frame = 1;

    serial_reset();
}



void vector_device::device_start()
{
    /* Grab the settings for this session */
    m_beam_width_min = machine().options().beam_width_min();
    m_beam_width_max = machine().options().beam_width_max();
    m_beam_intensity_weight = machine().options().beam_intensity_weight();
    m_flicker = machine().options().flicker();

    m_vector_index = 0;

    /* allocate memory for tables */
    m_vector_list = make_unique_clear<point[]>(MAX_POINTS);

    /* Setup the serial output of the XY coords if configured */
    m_serial = machine().options().vector_serial();
    const float scale = machine().options().vector_scale();
    if (scale != 0.0)
    {
        // user specified a scale on the command line
        m_serial_scale_x = m_serial_scale_y = scale;
    } else {
        // use the per-axis scales
        m_serial_scale_x = machine().options().vector_scale_x();
        m_serial_scale_y = machine().options().vector_scale_y();
    }

    m_serial_segments = m_serial_segments_tail = NULL;

    m_serial_offset_x = machine().options().vector_offset_x();
    m_serial_offset_y = machine().options().vector_offset_y();
    m_serial_rotate = machine().options().vector_rotate();
    m_serial_bright = machine().options().vector_bright();
    m_serial_drop_frame = 0;
    m_serial_sort = 1;

    // allocate enough buffer space, although we should never use this much
    m_serial_buf = auto_alloc_array_clear(machine(), unsigned char, (MAX_POINTS+2) * 4);
    if (!m_serial_buf)
    {
        // todo: how to signal an error?
    }

    serial_reset();

    if (!m_serial || strcmp(m_serial,"") == 0)
    {
        fprintf(stderr, "no serial vector display configured\n");
        m_serial_fd = -1;
    } else {
        m_serial_fd = serial_open(m_serial);
        fprintf(stderr, "serial dev='%s' fd=%d\n", m_serial, m_serial_fd);
    }
}

void vector_device::set_flicker(float newval)
{
    m_flicker = newval;
}

float vector_device::get_flicker()
{
    return m_flicker;
}

void vector_device::set_beam_width_min(float newval)
{
    m_beam_width_min = newval;
}

float vector_device::get_beam_width_min()
{
    return m_beam_width_min;
}

void vector_device::set_beam_width_max(float newval)
{
    m_beam_width_max = newval;
}

float vector_device::get_beam_width_max()
{
    return m_beam_width_max;
}

void vector_device::set_beam_intensity_weight(float newval)
{
    m_beam_intensity_weight = newval;
}

float vector_device::get_beam_intensity_weight()
{
    return m_beam_intensity_weight;
}


/*
 * www.dinodini.wordpress.com/2010/04/05/normalized-tunable-sigmoid-functions/
 */
float vector_device::normalized_sigmoid(float n, float k)
{
    // valid for n and k in range of -1.0 and 1.0
    return (n - n * k) / (k - fabs(n) * 2.0f * k + 1.0f);
}


/*
 * Adds a line end point to the vertices list. The vector processor emulation
 * needs to call this.
 */
void vector_device::add_point(int x, int y, rgb_t color, int intensity)
{
    point *newpoint;

//printf("%d %d: %d,%d,%d @ %d\n", x, y, color.r(), color.b(), color.g(), intensity);

    // hack for the vectrex
    // -- convert "128,128,128" @ 255 to "255,255,255" @ 127
    if (color.r() == 128
    &&  color.b() == 128
    &&  color.g() == 128
    &&  intensity == 255)
    {
        color = rgb_t(255,255,255);
        intensity = 128;
    }

    intensity = MAX(0, MIN(255, intensity));

    m_min_intensity = intensity > 0 ? MIN(m_min_intensity, intensity) : m_min_intensity;
    m_max_intensity = intensity > 0 ? MAX(m_max_intensity, intensity) : m_max_intensity;

    if (m_flicker && (intensity > 0))
    {
        float random = (float)(machine().rand() & 255) / 255.0f; // random value between 0.0 and 1.0

        intensity -= (int)(intensity * random * m_flicker);

        intensity = MAX(0, MIN(255, intensity));
    }

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x;
    newpoint->y = y;
    newpoint->col = color;
    newpoint->intensity = intensity;
    newpoint->status = VDIRTY; /* mark identical lines as clean later */

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * Add new clipping info to the list
 */
void vector_device::add_clip(int x1, int yy1, int x2, int y2)
{
    point *newpoint;

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x1;
    newpoint->y = yy1;
    newpoint->arg1 = x2;
    newpoint->arg2 = y2;
    newpoint->status = VCLIP;

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * The vector CPU creates a new display list. We save the old display list,
 * but only once per refresh.
 */
void vector_device::clear_list(void)
{
    m_vector_index = 0;
}


UINT32 vector_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
    UINT32 flags = PRIMFLAG_ANTIALIAS(screen.machine().options().antialias() ? 1 : 0) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD) | PRIMFLAG_VECTOR(1);
    const rectangle &visarea = screen.visible_area();
    float xscale = 1.0f / (65536 * visarea.width());
    float yscale = 1.0f / (65536 * visarea.height());
    float xoffs = (float)visarea.min_x;
    float yoffs = (float)visarea.min_y;
    float xratio = xscale / yscale;
    float yratio = yscale / xscale;
    xratio = (xratio < 1.0f) ? xratio : 1.0f;
    yratio = (yratio < 1.0f) ? yratio : 1.0f;

    point *curpoint;
    render_bounds clip;
    int lastx = 0;
    int lasty = 0;

    curpoint = m_vector_list.get();

    screen.container().empty();
    screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1));

    clip.x0 = clip.y0 = 0.0f;
    clip.x1 = clip.y1 = 1.0f;

    for (int i = 0; i < m_vector_index; i++)
    {
        render_bounds coords;

        if (curpoint->status == VCLIP)
        {
            coords.x0 = ((float)curpoint->x - xoffs) * xscale;
            coords.y0 = ((float)curpoint->y - yoffs) * yscale;
            coords.x1 = ((float)curpoint->arg1 - xoffs) * xscale;
            coords.y1 = ((float)curpoint->arg2 - yoffs) * yscale;

            clip.x0 = (coords.x0 > 0.0f) ? coords.x0 : 0.0f;
            clip.y0 = (coords.y0 > 0.0f) ? coords.y0 : 0.0f;
            clip.x1 = (coords.x1 < 1.0f) ? coords.x1 : 1.0f;
            clip.y1 = (coords.y1 < 1.0f) ? coords.y1 : 1.0f;
        }
        else
        {
            float beam_intensity_width = m_beam_width_min;

            float intensity = (float)curpoint->intensity / 255.0f;

            // check for dynamic intensity
            if (m_min_intensity != m_max_intensity)
            {
                float intensity_weight = normalized_sigmoid(intensity, m_beam_intensity_weight);
                beam_intensity_width = (m_beam_width_max - m_beam_width_min) * intensity_weight + m_beam_width_min;
            }

            float beam_width = beam_intensity_width * (1.0f / (float)VECTOR_WIDTH_DENOM);

            coords.x0 = ((float)lastx - xoffs) * xscale;
            coords.y0 = ((float)lasty - yoffs) * yscale;
            coords.x1 = ((float)curpoint->x - xoffs) * xscale;
            coords.y1 = ((float)curpoint->y - yoffs) * yscale;

            // extend zero-length vector line (vector point) by quarter beam_width on both sides
            if (fabs(coords.x0 - coords.x1) < FLT_EPSILON &&
                fabs(coords.y0 - coords.y1) < FLT_EPSILON)
            {
                coords.x0 += xratio * beam_width * 0.25f;
                coords.y0 += yratio * beam_width * 0.25f;
                coords.x1 -= xratio * beam_width * 0.25f;
                coords.y1 -= yratio * beam_width * 0.25f;
            }

            if (curpoint->intensity != 0 && !render_clip_line(&coords, &clip))
            {
                screen.container().add_line(
                    coords.x0, coords.y0, coords.x1, coords.y1,
                    beam_width,
                    (curpoint->intensity << 24) | (curpoint->col & 0xffffff),
                    flags);

                serial_draw_line(
                    coords.x0, coords.y0,
                    coords.x1, coords.y1,
                    curpoint->intensity);
            }

            lastx = curpoint->x;
            lasty = curpoint->y;
        }

        curpoint++;
    }

    serial_send();

    return 0;
}

アドベンチャーゲーム

ZORK1を攻略しました。 tms9918.hatenablog.com

ソースコードもあります。 github.com

Vezzaを使ってZORK1やColossal Cave AdventureをMSXで遊ぶことが出来ます。CP/M版ZORK1.DATはZ3ファイルのようです。 gitlab.com

zmachine-multilingualを使って、CP/M版ZORK1を日本語で遊ぶことが出来ます。 github.com

$ cat ZORK1.DAT > zork1.z3
$ sbcl --script run-zork.lisp
Loaded: translations-ja.lisp
Loaded user file: translations-ja.lisp
Language: Japanese (日本語)
Bilingual mode: enabled
Translations loaded: 169
Auto-save: enabled
Loaded Z-machine version 3 story file
  Dynamic memory: 0 - 2E53
  High memory: 4E37 - 14C00
  Initial PC: 4F05

ZORK I: The Great Underground Empire
Copyright (c) 1981, 1982, 1983 Infocom, Inc. All rights reserved.
ZORK is a registered trademark of Infocom, Inc.
Revision 88 / Serial number 840726

West of House
You are standing in an open field west of a white house, with a boarded front door.
There is a small mailbox here.

家の西側
あなたは白い家の西側の開けた野原に立っています。正面のドアは板で塞がれています。
ここに小さな郵便受けがあります。
>

Colossal Cave Adventureを攻略しました。 tms9918.hatenablog.com

zmachine-multilingualを使ってColossal Cave Adventureも日本語で遊ぶことが出来ます。 microheaven.com

$ sbcl --script run-advent.lisp
Loaded: translations-ja.lisp
Loaded user file: translations-ja.lisp
Language: Japanese (日本語)
Bilingual mode: enabled
Translations loaded: 169
Auto-save: enabled
DeepL API configured.
Loaded Z-machine version 3 story file
  Dynamic memory: 0 - 35AA
  High memory: 4E7E - 13400
  Initial PC: 4E7F

Welcome to Adventure!
(Please type HELP for instructions and information.)

ADVENTURE
The Interactive Original
By Will Crowther (1976) and Don Woods (1977)
Reconstructed in three steps by:
Donald Ekman, David M. Baggett (1993) and Graham Nelson (1994)
PunyInform version: Fredrik Ramsberg (2024)
[In memoriam Stephen Bishop (1820?-1857): GN]

Release 9 / Serial number 260512 / Inform v6.45 PunyInform v6.6

At End Of Road
You are standing at the end of a road before a small brick building. Around you is a forest. A small stream flows out of the building and down a gully.

アドベンチャーへようこそ!
(操作方法や情報については、「HELP」と入力してください。)
アドベンチャー
インタラクティブ・オリジナル
ウィル・クロウザー(1976年)およびドン・ウッズ(1977年)作
3段階に分けて再構築:
ドナルド・エクマン、デビッド・M・バゲット(1993年)、グラハム・ネルソン (1994年)
PunyInform版:フレドリック・ラムスバーグ(2024年)
[スティーブン・ビショップ(1820?–1857)を追悼して:GN]
リリース 9 / シリアル番号 260512 / Inform v6.45 PunyInform v6.6
道の果てで
あなたは道の突き当たり、小さなレンガ造りの建物の前に立っています。周囲は森に囲まれています。建物から小さな小川が流れ出し、谷間へと下っています。
>

Fortran版をPDP-11で遊ぶことが出来ます。 trmm.net

Fortran版のADVENT.DATをPythonで遊ぶことが出来ます。 github.com

Fortran版の一番古いものはこちらのようです。 github.com

Fortran版のソースコードを読むのもいいですし、ZIL版のソースコードを読むのもいいですね。 zilf.io

SEGA GENESIS COLLECTIONを解析してみた!

米国版PS2ソフトになります。 www.amazon.com

ここらへんを参考にして解析してみる。

github.com

例えばTac/Scanであれば、TACSCAN.SRを展開してTACSCAN.ROMを生成します。

$ python xsr.py TACSCAN.SR
files: 22
./ic_TACSCAN.IA 0 11856
./ic_TACSCAN.PNG 12288 31859
./TACSCAN.ROM 45056 45056
./TacScanS18.wav 90112 24750
./TacScanS1C.wav 116736 45830
./TacScanS20.wav 163840 16766
./TacScanS28.wav 182272 24278
./TacScanS2C.wav 206848 45830
./TacScanS31.wav 253952 64206
./TacScanS32.wav 319488 67772
./TacScanS33.wav 389120 73858
./TacScanS34.wav 464896 69918
./TacScanS35.wav 536576 71846
./TacScanS36.wav 610304 74070
./TacScanS37.wav 686080 74970
./TacScanS37A.wav 761856 61740
./TacScanS48.wav 825344 7874
./TacScanS50.wav 833536 5036
./TacScanS51.wav 839680 2528
./TacScanS54.wav 843776 83748
./TacScanS60.wav 927744 17030
./TacScanS6C.wav 946176 37510

CRCを調べて切り出します。 https://github.com/mamedev/mame/blob/master/src/mame/sega/segag80v.cpp

ROM_START( tacscan )
    ROM_REGION( 0xc000, "maincpu", 0 )
    ROM_LOAD( "1711a.cpu-u25",  0x0000, 0x0800, CRC(0da13158) SHA1(256c5441a4841441501c9b7bcf09e0e99e8dd671) )
    ROM_LOAD( "1670c.prom-u1",  0x0800, 0x0800, CRC(98de6fd5) SHA1(f22c215d7558e00366fec5092abb51c670468f8c) )
    ROM_LOAD( "1671a.prom-u2",  0x1000, 0x0800, CRC(dc400074) SHA1(70093ef56e0784173a06da1ac781bb9d8c4e7fc5) )
    ROM_LOAD( "1672a.prom-u3",  0x1800, 0x0800, CRC(2caf6f7e) SHA1(200119260f78bb1c5389707b3ceedfbc1ae43549) )
    ROM_LOAD( "1673a.prom-u4",  0x2000, 0x0800, CRC(1495ce3d) SHA1(3189f8061961d90a52339c855c06e81f4537fb2b) )
    ROM_LOAD( "1674a.prom-u5",  0x2800, 0x0800, CRC(ab7fc5d9) SHA1(b2d9241d83d175ead4da36d7311a41a5f972e06a) )
    ROM_LOAD( "1675a.prom-u6",  0x3000, 0x0800, CRC(cf5e5016) SHA1(78a3f1e4a905515330d4737ac38576ac6e0d8611) )
    ROM_LOAD( "1676a.prom-u7",  0x3800, 0x0800, CRC(b61a3ab3) SHA1(0f4ef5c7fe299ad20fa4637260282a733f1cf461) )
    ROM_LOAD( "1677a.prom-u8",  0x4000, 0x0800, CRC(bc0273b1) SHA1(8e8d8830f17b9fa6d45d98108ca02d90c29de574) )
    ROM_LOAD( "1678b.prom-u9",  0x4800, 0x0800, CRC(7894da98) SHA1(2de7c121ad847e51a10cb1b81aec84cc44a3d04c) )
    ROM_LOAD( "1679a.prom-u10", 0x5000, 0x0800, CRC(db865654) SHA1(db4d5675b53ff2bbaf70090fd064e98862f4ad33) )
    ROM_LOAD( "1680a.prom-u11", 0x5800, 0x0800, CRC(2c2454de) SHA1(74101806439c9faeba88ffe573fa4f93ffa0ba3c) )
    ROM_LOAD( "1681a.prom-u12", 0x6000, 0x0800, CRC(77028885) SHA1(bc981620ebbfbe4e32b3b4d00504475634454c57) )
    ROM_LOAD( "1682a.prom-u13", 0x6800, 0x0800, CRC(babe5cf1) SHA1(26219b7a26f818fee2fe579ec6fb0b16c6bf056f) )
    ROM_LOAD( "1683a.prom-u14", 0x7000, 0x0800, CRC(1b98b618) SHA1(19854cb2741ba37c11ae6d429fa6c17ff930f5e5) )
    ROM_LOAD( "1684a.prom-u15", 0x7800, 0x0800, CRC(cb3ded3b) SHA1(f1e886f4f71b0f6f2c11fb8b4921c3452fc9b2c0) )
    ROM_LOAD( "1685a.prom-u16", 0x8000, 0x0800, CRC(43016a79) SHA1(ee22c1fe0c8df90d9215175104f8a796c3d2aed3) )
    ROM_LOAD( "1686a.prom-u17", 0x8800, 0x0800, CRC(a4397772) SHA1(cadc95b869f5bf5dba7f03dfe5ae64a50899cced) )
    ROM_LOAD( "1687a.prom-u18", 0x9000, 0x0800, CRC(002f3bc4) SHA1(7f3795a05d5651c90cdcd4d00c46d05178b433ea) )
    ROM_LOAD( "1688a.prom-u19", 0x9800, 0x0800, CRC(0326d87a) SHA1(3a5ea4526db417b9e00b24b019c1c6016773c9e7) )
    ROM_LOAD( "1709a.prom-u20", 0xa000, 0x0800, CRC(f35ed1ec) SHA1(dce95a862af0c6b67fb76b99fee0523d53b7551c) )
    ROM_LOAD( "1710a.prom-u21", 0xa800, 0x0800, CRC(6203be22) SHA1(89731c7c88d0125a11368d707f566eb53c783266) )

    ROM_REGION( 0x0420, "proms", 0 )
    ROM_LOAD( "s-c.xyt-u39",    0x0000, 0x0400, CRC(56484d19) SHA1(61f43126fdcfc230638ed47085ae037a098e6781) )  // sine table
    ROM_LOAD( "pr-82.cpu-u15",  0x0400, 0x0020, CRC(c609b79e) SHA1(49dbcbb607079a182d7eb396c0da097166ea91c9) )  // CPU board addressing
ROM_END

PROM以外はCRC32が一致しました!

$ ./crc
Usage: crc filename crc32 size [addr]
$ ./crc TACSCAN.ROM 0da13158 800
crc32=0da13158,size=0800
crc32=0da13158,addr=0000-07ff
$ ./crc TACSCAN.ROM 98de6fd5 800
crc32=98de6fd5,size=0800
crc32=98de6fd5,addr=0800-0fff
$ ./crc TACSCAN.ROM dc400074 800
crc32=dc400074,size=0800
crc32=dc400074,addr=1000-17ff
$ ./crc TACSCAN.ROM 2caf6f7e 800
crc32=2caf6f7e,size=0800
crc32=2caf6f7e,addr=1800-1fff
$ ./crc TACSCAN.ROM 1495ce3d 800
crc32=1495ce3d,size=0800
crc32=1495ce3d,addr=2000-27ff
$ ./crc TACSCAN.ROM ab7fc5d9 800
crc32=ab7fc5d9,size=0800
crc32=ab7fc5d9,addr=2800-2fff
$ ./crc TACSCAN.ROM cf5e5016 800
crc32=cf5e5016,size=0800
crc32=cf5e5016,addr=3000-37ff
$ ./crc TACSCAN.ROM b61a3ab3 800
crc32=b61a3ab3,size=0800
crc32=b61a3ab3,addr=3800-3fff
$ ./crc TACSCAN.ROM bc0273b1 800
crc32=bc0273b1,size=0800
crc32=bc0273b1,addr=4000-47ff
$ ./crc TACSCAN.ROM 7894da98 800
crc32=7894da98,size=0800
crc32=7894da98,addr=4800-4fff
$ ./crc TACSCAN.ROM db865654 800
crc32=db865654,size=0800
crc32=db865654,addr=5000-57ff
$ ./crc TACSCAN.ROM 2c2454de 800
crc32=2c2454de,size=0800
crc32=2c2454de,addr=5800-5fff
$ ./crc TACSCAN.ROM 77028885 800
crc32=77028885,size=0800
crc32=77028885,addr=6000-67ff
$ ./crc TACSCAN.ROM babe5cf1 800
crc32=babe5cf1,size=0800
crc32=babe5cf1,addr=6800-6fff
$ ./crc TACSCAN.ROM 1b98b618 800
crc32=1b98b618,size=0800
crc32=1b98b618,addr=7000-77ff
$ ./crc TACSCAN.ROM cb3ded3b 800
crc32=cb3ded3b,size=0800
crc32=cb3ded3b,addr=7800-7fff
$ ./crc TACSCAN.ROM 43016a79 800
crc32=43016a79,size=0800
crc32=43016a79,addr=8000-87ff
$ ./crc TACSCAN.ROM a4397772 800
crc32=a4397772,size=0800
crc32=a4397772,addr=8800-8fff
$ ./crc TACSCAN.ROM 002f3bc4 800
crc32=002f3bc4,size=0800
crc32=002f3bc4,addr=9000-97ff
$ ./crc TACSCAN.ROM 0326d87a 800
crc32=0326d87a,size=0800
crc32=0326d87a,addr=9800-9fff
$ ./crc TACSCAN.ROM f35ed1ec 800
crc32=f35ed1ec,size=0800
crc32=f35ed1ec,addr=a000-a7ff
$ ./crc TACSCAN.ROM 6203be22 800
crc32=6203be22,size=0800
crc32=6203be22,addr=a800-afff
$ ./crc TACSCAN.ROM 56484d19 400
crc32=56484d19,size=0400
$ ./crc TACSCAN.ROM c609b79e 20
crc32=c609b79e,size=0020

リネームしたものをtacscan.zipにまとめて、mame/roms配下に置いて起動してみます。

>mame.exe tacscan
s-c.xyt-u39 NOT FOUND (tried in tacscan)
pr-82.cpu-u15 NOT FOUND (tried in tacscan)
Fatal error: Required files are missing, the machine cannot be run.

最近のMAMEではROMが不足しているようですが、古いMAME(mame070b.zip)ではPROMは不要です。