604 lines
13 KiB
C
604 lines
13 KiB
C
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
#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<len; x++) {
|
|
_buffer[_bufferHead++] = data[x];
|
|
if (_bufferHead >= 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.
|
|
}
|
|
|