/* * Kangaroo Punch MultiPlayer Game Server Mark II * Copyright (C) 2020-2021 Scott Duensing * * 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 . * */ #include #define ENET_IMPLEMENTATION #include "thirdparty/enet/include/enet.h" #include "os.h" #include "mouse.h" #include "vesa.h" #include "surface.h" #include "comport.h" #define SECONDS_IN_DAY 86400 #define TICKS_PER_SECOND 18.2 #define TICKS_PER_DAY (SECONDS_IN_DAY * TICKS_PER_SECOND) #define TIMER_INTERVAL (1000 / TICKS_PER_SECOND) #define COM_BUFFER_SIZE 2048 #define MODEM_RESULT_OK 1 #define MODEM_RESULT_ERROR 2 static MouseT _mouse; static SDL_Window *_window = NULL; static SDL_Renderer *_renderer = NULL; static SDL_Surface *_surface = NULL; static SDL_Texture *_texture = NULL; static uint16_t _width = 0; static uint16_t _height = 0; static uint8_t _windowScale = 1; static uint8_t _alt = 0; static uint8_t _control = 0; static uint8_t _shift = 0; static uint8_t _ASCII = 0; static uint8_t _scanCode = 0; static uint8_t _extended = 0; static uint8_t _ASCIIKeep = 0; static uint8_t _scanCodeKeep = 0; static uint8_t _keyPressed = 0; static uint8_t _debounce = 0; static long _timerTicks = 0; static SDL_TimerID _timerID; static ENetHost *_host = NULL; static ENetPeer *_peer = NULL; static ENetAddress _address; static uint8_t _comPortOpen = 0; static uint8_t _modemCommandMode = 1; static char _buffer[COM_BUFFER_SIZE]; static uint16_t _bufferHead = 0; static uint16_t _bufferTail = 0; static uint8_t _connected = 0; static void comAddToBuffer(char *data, uint16_t len); static void comModem(uint8_t c); static void processEvent(void); static void processNetworkEvent(void); long biostime(int cmd, long newtime) { (void)cmd; (void)newtime; return _timerTicks; } static void comAddToBuffer(char *data, uint16_t len) { uint16_t x; for (x=0; x= COM_BUFFER_SIZE) _bufferHead = 0; } } int comClose(int com) { // We only pretend to support one COM port. if (com != 0) return SER_ERR_UNKNOWN; if (!_comPortOpen) return SER_ERR_NOT_OPEN; if (_peer) { enet_peer_reset(_peer); _peer = NULL; } if (_host) { enet_host_destroy(_host); _host = NULL; } _comPortOpen = 0; return SER_SUCCESS; } static void comModem(uint8_t c) { static char command[COM_BUFFER_SIZE] = { 0 }; uint16_t x; uint8_t result = 0; uint8_t parsing = 1; char *p = NULL; if (c != 13) { x = strlen(command); // Room in buffer? if (x > COM_BUFFER_SIZE - 1) { // No. Error. Clear buffer. snprintf(command, COM_BUFFER_SIZE, "%cERROR%c", 13, 13); comAddToBuffer(command, strlen(command)); command[0] = 0; } else { // Add to command buffer. command[x] = toupper(c); command[x+1] = 0; } } else { // AT? if (command[0] != 'A' || command[1] != 'T') result = MODEM_RESULT_ERROR; // Process command string. x = 2; while (parsing && !result && command[x]) { switch (command[x]) { // Vendor Commands case '+': x++; // Select Socket Mode if (strncmp(&command[x], "SOCK", 4) == 0) { result = MODEM_RESULT_OK; } else { result = MODEM_RESULT_ERROR; } break; // Dial case 'D': x++; if (command[x] == 'T' || command[x] == 'P') x++; // Find port, if any. _address.port = 23; if ((p = strstr(&command[x], ":"))) { *p = 0; p++; _address.port = atoi(p); } if (enet_address_set_host(&_address, &command[x]) < 0) { result = MODEM_RESULT_ERROR; } else { _host = enet_host_create(NULL, 1, 1, 0, 0); if (!_host) { result = MODEM_RESULT_ERROR; } else { _peer = enet_host_connect(_host, &_address, 1, 0); if (!_peer) { enet_host_destroy(_host); _host = NULL; result = MODEM_RESULT_ERROR; } else { result = MODEM_RESULT_OK; } } } _modemCommandMode = 0; break; // Dunno. default: result = MODEM_RESULT_ERROR; break; } } // Send result. switch (result) { case MODEM_RESULT_OK: snprintf(command, COM_BUFFER_SIZE, "OK%c", 13); comAddToBuffer(command, strlen(command)); break; case MODEM_RESULT_ERROR: snprintf(command, COM_BUFFER_SIZE, "ERROR%c", 13); comAddToBuffer(command, strlen(command)); break; } // Clear buffer. command[0] = 0; } } int comOpen(int com, long bps, int dataBits, char parity, int stopBits, int handshaking) { (void)bps; (void)dataBits; (void)parity; (void)stopBits; (void)handshaking; // We only pretend to support one COM port. if (com != 0) return SER_ERR_UNKNOWN; if (_comPortOpen) return SER_ERR_ALREADY_OPEN; _comPortOpen = 1; _bufferHead = 0; _bufferTail = 0; // We really shouldn't do this, but enet is being stupid about not sending a DISCONNECT_TIMEOUT when failing to connect. _modemCommandMode = 1; return SER_SUCCESS; } int comRead(int com, char *data, int len) { uint16_t x = 0; // We only pretend to support one COM port. if (com != 0) return SER_ERR_UNKNOWN; if (!_comPortOpen) return SER_ERR_NOT_OPEN; // Is the buffer empty? if (_bufferHead == _bufferTail) { return 0; } // Fill buffer. while (x < len && _bufferHead != _bufferTail) { data[x++] = _buffer[_bufferTail++]; if (_bufferTail >= COM_BUFFER_SIZE) _bufferTail = 0; } return x; } int comWrite(int com, const char *data, int len) { uint16_t x = 0; ENetPacket *packet = NULL; // We only pretend to support one COM port. if (com != 0) return SER_ERR_UNKNOWN; if (!_comPortOpen) return SER_ERR_NOT_OPEN; while (_modemCommandMode && x < len) { comAddToBuffer((char *)&data[x], 1); comModem(data[x++]); } if (!_modemCommandMode && _connected && len - x > 0) { packet = enet_packet_create(&data[x], len - x, ENET_PACKET_FLAG_RELIABLE); if (!packet) return SER_ERR_UNKNOWN; enet_peer_send(_peer, 0, packet); } return SER_SUCCESS; } uint8_t keyAltGet(void) { return _alt; } uint8_t keyASCIIGet(void) { return _ASCIIKeep; } uint8_t keyControlGet(void) { return _control; } uint8_t keyExtendedGet(void) { return _extended; } uint8_t keyHit(void) { uint8_t result = 0; processEvent(); result = _keyPressed; if (result) { // Default mapping. _extended = 0; _ASCIIKeep = _ASCII; _scanCodeKeep = _scanCode; _keyPressed = 0; // Fix mappings to match DOS bioskey() call. if (_scanCodeKeep == SDL_SCANCODE_ESCAPE) _ASCIIKeep = 27; if (_scanCodeKeep == SDL_SCANCODE_BACKSPACE) _ASCIIKeep = 8; if (_scanCodeKeep == SDL_SCANCODE_HOME) { _extended = 1; _ASCIIKeep = 71; } if (_scanCodeKeep == SDL_SCANCODE_UP) { _extended = 1; _ASCIIKeep = 72; } if (_scanCodeKeep == SDL_SCANCODE_PAGEUP) { _extended = 1; _ASCIIKeep = 73; } if (_scanCodeKeep == SDL_SCANCODE_LEFT) { _extended = 1; _ASCIIKeep = 75; } if (_scanCodeKeep == SDL_SCANCODE_RIGHT) { _extended = 1; _ASCIIKeep = 77; } if (_scanCodeKeep == SDL_SCANCODE_END) { _extended = 1; _ASCIIKeep = 79; } if (_scanCodeKeep == SDL_SCANCODE_DOWN) { _extended = 1; _ASCIIKeep = 80; } if (_scanCodeKeep == SDL_SCANCODE_PAGEDOWN) { _extended = 1; _ASCIIKeep = 81; } if (_scanCodeKeep == SDL_SCANCODE_DELETE) { _extended = 1; _ASCIIKeep = 83; } } return result; } uint8_t keyScanCodeGet(void) { return _scanCode; } uint8_t keyShiftGet(void) { return _shift; } void keyShutdown(void) { SDL_StopTextInput(); } void keyStartup(void) { SDL_StartTextInput(); } void mouseShutdown(void) { // Eh, don't care. } int16_t mouseStartup(void) { _mouse.active = 1; _mouse.buttonCount = 3; _mouse.buttonLeft = 0; _mouse.buttonRight = 0; _mouse.buttonMiddle = 0; _mouse.buttonLeftWasDown = 0; _mouse.buttonRightWasDown = 0; _mouse.buttonMiddleWasDown = 0; _mouse.x = 0; _mouse.y = 0; _mouse.w = vbeDisplayWidthGet(); _mouse.h = vbeDisplayHeightGet(); return _mouse.active; } MouseT *mouseRead(void) { _mouse.buttonLeftWasDown = _mouse.buttonLeft; _mouse.buttonRightWasDown = _mouse.buttonRight; _mouse.buttonMiddleWasDown = _mouse.buttonMiddle; processEvent(); return &_mouse; } static void processEvent(void) { SDL_Event e; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_MOUSEMOTION: _mouse.x = e.motion.x; _mouse.y = e.motion.y; break; case SDL_MOUSEBUTTONUP: if (e.button.button == SDL_BUTTON_LEFT) _mouse.buttonLeft = 0; if (e.button.button == SDL_BUTTON_RIGHT) _mouse.buttonRight = 0; if (e.button.button == SDL_BUTTON_MIDDLE) _mouse.buttonMiddle = 0; break; case SDL_MOUSEBUTTONDOWN: if (e.button.button == SDL_BUTTON_LEFT) _mouse.buttonLeft = 1; if (e.button.button == SDL_BUTTON_RIGHT) _mouse.buttonRight = 1; if (e.button.button == SDL_BUTTON_MIDDLE) _mouse.buttonMiddle = 1; break; case SDL_TEXTINPUT: _ASCII = e.text.text[0]; //***TODO*** This is horrifically wrong. break; case SDL_KEYUP: _scanCode = 0; _ASCII = 0; _keyPressed = 0; _debounce = 0; break; case SDL_KEYDOWN: if (_debounce == 0) { _scanCode = e.key.keysym.scancode; _shift = ((SDL_GetModState() & KMOD_SHIFT) != 0); _alt = ((SDL_GetModState() & KMOD_ALT) != 0); _control = ((SDL_GetModState() & KMOD_CTRL) != 0); if (e.key.keysym.scancode != 0) { // Not a meta key _keyPressed = 1; _debounce = 1; } } break; } } processNetworkEvent(); } static void processNetworkEvent(void) { ENetEvent event = { 0 }; if (_host) { while (enet_host_service(_host, &event, 1) > 0) { switch (event.type) { case ENET_EVENT_TYPE_CONNECT: _connected = 1; break; case ENET_EVENT_TYPE_RECEIVE: comAddToBuffer((char *)event.packet->data, event.packet->dataLength); break; case ENET_EVENT_TYPE_DISCONNECT: case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: _connected = 0; _modemCommandMode = 1; comAddToBuffer("\13NO CARRIER\13", 12); break; default: break; } } } } uint32_t timerCallback(uint32_t interval, void *param) { (void)param; _timerTicks++; if (_timerTicks > TICKS_PER_DAY) { _timerTicks = 0; } return interval; } uint8_t vbeDisplayDepthBitsGet(void) { return 32; } uint8_t vbeDisplayDepthBytesGet(void) { return 4; } uint16_t vbeDisplayHeightGet(void) { return _height; } uint16_t vbeDisplayWidthGet(void) { return _width; } ColorT vbeColorMake(uint8_t red, uint8_t green, uint8_t blue) { // Pixels are 8:8:8:8 RGBA - SDL_PIXELFORMAT_RGBA8888 return (red << 24) | (green << 16) | (blue << 8) | 255; } void vbePresent(void) { void *pixels; int temp; SurfaceT *s = surfaceOffScreenGet(); SDL_LockTexture(_texture, NULL, &pixels, &temp); memcpy(pixels, s->buffer.bits8, s->bytes); SDL_UnlockTexture(_texture); SDL_RenderCopy(_renderer, _texture, NULL, NULL); SDL_RenderPresent(_renderer); if (_host) { // Throttle this to some sane frame rate by checking for network events. for (temp=0; temp<32; temp++) { processNetworkEvent(); } } else { // No network activity. Just sleep. SDL_Delay(32); } } int16_t vbeInfoShow(void) { // Eh, don't care. return 0; } int16_t vbeShutdown(void) { if (_texture) { SDL_DestroyTexture(_texture); _texture = NULL; } if (_renderer) { SDL_DestroyRenderer(_renderer); _renderer = NULL; } if (_window) { SDL_DestroyWindow(_window); _window = NULL; } // Stop network. enet_deinitialize(); // Stop timer. SDL_RemoveTimer(_timerID); SDL_Quit(); return 0; } uint8_t vbeStartup(uint16_t xRes, uint16_t yRes, uint8_t bpp) { (void)bpp; _windowScale = 3; SDL_Init(SDL_INIT_EVERYTHING); _window = SDL_CreateWindow("GUI Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, xRes, yRes, SDL_WINDOW_ALLOW_HIGHDPI); _surface = SDL_GetWindowSurface(_window); _renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED); _texture = SDL_CreateTexture(_renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, xRes, yRes); SDL_RenderSetLogicalSize(_renderer, xRes, yRes); SDL_SetWindowSize(_window, xRes * _windowScale, yRes * _windowScale); _width = xRes; _height = yRes; // We do timer startup here, too. _timerID = SDL_AddTimer(TIMER_INTERVAL, timerCallback, NULL); // And networking/serial startup. enet_initialize(); return 0; } void vbeVBlankWait(void) { // Eh, don't care. }