/* * VWE fork: in-fork cockpit plasma display (serial=plasma). See * serialplasma.h. Graphics-only (ESC P) transcription of vPLASMA's device; * receive-only, single emulator thread except the framebuffer snapshot which * the VPX render thread reads under a lock. */ #include "dosbox.h" #include "logging.h" #include "pic.h" #include "serialport.h" #include "serialplasma.h" #include #include #include #include #define PLASMA_ESC 0x1B static CSerialPlasma* plasma_instance = nullptr; bool PLASMA_GetFrame(uint8_t out[512]) { if (!plasma_instance) return false; plasma_instance->getFrame(out); return true; } CSerialPlasma::CSerialPlasma(Bitu id, CommandLine* cmd) : CSerial(id, cmd) { (void)cmd; InstallationSuccessful = false; logv = (getenv("VWE_PLASMA_LOG") != nullptr); LOG_MSG("Serial%d: in-fork plasma display (128x32, ESC P graphics)", (int)COMNUMBER); CSerial::Init_Registers(); // the display is always present -- assert the modem-in lines the game reads setRI(false); setCD(true); setDSR(true); setCTS(true); if (plasma_instance == nullptr) plasma_instance = this; else LOG_MSG("Serial%d: another plasma port already owns the display window", (int)COMNUMBER); InstallationSuccessful = true; } CSerialPlasma::~CSerialPlasma() { if (plasma_instance == this) plasma_instance = nullptr; } void CSerialPlasma::logf(const char* fmt, ...) { if (!logv) return; va_list ap; va_start(ap, fmt); fprintf(stderr, "[plasma] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } void CSerialPlasma::getFrame(uint8_t out[512]) { std::lock_guard lk(fbMx); memcpy(out, fb, 512); } // ---- ESC command parser (VPlasmaDevice.Step, graphics path) ------------------ void CSerialPlasma::feed(uint8_t b) { switch (st) { case P_TEXT: // The game is graphics-only; a printable/control byte in text mode is // not something it sends, so swallow it (text mode isn't rendered). if (b == PLASMA_ESC) st = P_ESC; return; case P_ESC: st = P_TEXT; switch (b) { case '@': // clear screen { std::lock_guard lk(fbMx); memset(fb, 0, 512); } logf("clear (ESC @)"); break; case 'L': // home cursor -- no-op for graphics break; case 'G': case 'K': case 'H': // cursor mode / font / attributes case 'Q': case 'R': // set row / column case 'I': case 'i': // draw / display page st = P_OPERAND; // consume 1 operand, unused (text mode) break; case 'P': // graphics write hdrFill = 0; st = P_GHDR; break; default: logf("unknown ESC 0x%02X ('%c')", b, (b >= 0x20 && b < 0x7F) ? (char)b : '?'); break; } return; case P_OPERAND: st = P_TEXT; // operand read + discarded return; case P_GHDR: hdr[hdrFill++] = b; if (hdrFill == 5) { // screen, y, xbyte, w, h int w = hdr[3], h = hdr[4]; dataLen = w * h; dataIdx = 0; logf("ESC P y=%u xbyte=%u %ux%u", hdr[1], hdr[2], (unsigned)w, (unsigned)h); st = dataLen > 0 ? P_GDATA : P_TEXT; } return; case P_GDATA: { int w = hdr[3]; int rowOfBlock = w > 0 ? dataIdx / w : 0; int byteOfRow = w > 0 ? dataIdx % w : 0; int y = hdr[1] + rowOfBlock; // 0..31 int xByte = hdr[2] + byteOfRow; // 0..15 // MSB = leftmost pixel; the game packs whole rows (xbyte=0, w=16). if (y >= 0 && y < 32 && xByte >= 0 && xByte < 16) { std::lock_guard lk(fbMx); fb[y * 16 + xByte] = b; } if (byteOfRow == w - 1 && y >= 0 && y < 32) graphicsRows++; if (++dataIdx >= dataLen) { st = P_TEXT; if ((graphicsRows % 200) == 0) logf("rows drawn: %ld", graphicsRows); } } return; } } // ---- UART plumbing (receive-only; drain the game's TX like the pipe) --------- void CSerialPlasma::transmitByte(uint8_t val, bool first) { if (first) setEvent(SERIAL_THR_EVENT, bytetime / 10); else setEvent(SERIAL_TX_EVENT, bytetime); feed(val); } void CSerialPlasma::handleUpperEvent(uint16_t type) { if (type == SERIAL_THR_EVENT) { ByteTransmitting(); setEvent(SERIAL_TX_EVENT, bytetime * 1.1f); } else if (type == SERIAL_TX_EVENT) { ByteTransmitted(); } } void CSerialPlasma::updatePortConfig(uint16_t divider, uint8_t lcr) { (void)divider; (void)lcr; } void CSerialPlasma::updateMSR() {} void CSerialPlasma::setBreak(bool value) { (void)value; } void CSerialPlasma::setRTSDTR(bool rts, bool dtr) { (void)rts; (void)dtr; } void CSerialPlasma::setRTS(bool val) { (void)val; } void CSerialPlasma::setDTR(bool val) { (void)val; }