DVX_GUI/dvx/platform/dvxPlatformDos.c

904 lines
24 KiB
C

// dvxPlatformDos.c — DOS/DJGPP platform implementation for DVX GUI
//
// All BIOS calls, DPMI functions, port I/O, inline assembly, and
// DOS-specific file handling are isolated in this single file.
#include "dvxPlatform.h"
#include "../dvxPalette.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <sys/nearptr.h>
#include <sys/farptr.h>
// ============================================================
// Prototypes
// ============================================================
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
static int32_t setVesaMode(uint16_t mode);
// Alt+key scan code to ASCII lookup table (indexed by BIOS scan code).
// INT 16h returns these scan codes with ascii=0 for Alt+key combos.
static const char sAltScanToAscii[256] = {
// Alt+letters
[0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r',
[0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i',
[0x18] = 'o', [0x19] = 'p', [0x1E] = 'a', [0x1F] = 's',
[0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h',
[0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x2C] = 'z',
[0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', [0x30] = 'b',
[0x31] = 'n', [0x32] = 'm',
// Alt+digits
[0x78] = '1', [0x79] = '2', [0x7A] = '3', [0x7B] = '4',
[0x7C] = '5', [0x7D] = '6', [0x7E] = '7', [0x7F] = '8',
[0x80] = '9', [0x81] = '0',
};
// ============================================================
// findBestMode
// ============================================================
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d) {
__dpmi_regs r;
uint16_t bestMode = 0;
int32_t bestScore = -1;
DisplayT bestDisplay;
memset(&bestDisplay, 0, sizeof(bestDisplay));
// Get VBE controller info
uint32_t infoSeg = __tb >> 4;
uint32_t infoOff = __tb & 0x0F;
// Write "VBE2" at transfer buffer to request VBE 2.0 info
_farpokeb(_dos_ds, __tb + 0, 'V');
_farpokeb(_dos_ds, __tb + 1, 'B');
_farpokeb(_dos_ds, __tb + 2, 'E');
_farpokeb(_dos_ds, __tb + 3, '2');
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F00;
r.x.es = infoSeg;
r.x.di = infoOff;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax);
return -1;
}
// Verify VBE signature
char sig[5];
for (int32_t i = 0; i < 4; i++) {
sig[i] = _farpeekb(_dos_ds, __tb + i);
}
sig[4] = '\0';
if (strcmp(sig, "VESA") != 0) {
fprintf(stderr, "VBE: Bad signature '%s'\n", sig);
return -1;
}
// Check VBE version (need 2.0+)
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
if (vbeVersion < 0x0200) {
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
vbeVersion >> 8, vbeVersion & 0xFF);
return -1;
}
// Get mode list pointer (far pointer at offset 14)
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
// Walk mode list
for (int32_t i = 0; i < 256; i++) {
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
if (mode == 0xFFFF) {
break;
}
DisplayT candidate;
int32_t score = 0;
memset(&candidate, 0, sizeof(candidate));
getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp);
if (score > bestScore) {
bestScore = score;
bestMode = mode;
bestDisplay = candidate;
}
}
if (bestScore < 0) {
fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH);
return -1;
}
*outMode = bestMode;
*d = bestDisplay;
return 0;
}
// ============================================================
// platformVideoEnumModes
// ============================================================
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F00;
r.x.es = __tb >> 4;
r.x.di = __tb & 0x0F;
// Write "VBE2" signature to request VBE 2.0+ info
_farpokeb(_dos_ds, __tb + 0, 'V');
_farpokeb(_dos_ds, __tb + 1, 'B');
_farpokeb(_dos_ds, __tb + 2, 'E');
_farpokeb(_dos_ds, __tb + 3, '2');
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
return;
}
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
for (int32_t i = 0; i < 256; i++) {
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
if (mode == 0xFFFF) {
break;
}
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F01;
r.x.cx = mode;
r.x.es = __tb >> 4;
r.x.di = __tb & 0x0F;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
continue;
}
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
// Only report LFB-capable graphics modes
if (!(attr & 0x0080) || !(attr & 0x0010)) {
continue;
}
int32_t w = _farpeekw(_dos_ds, __tb + 18);
int32_t h = _farpeekw(_dos_ds, __tb + 20);
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
cb(w, h, bpp, userData);
}
}
// ============================================================
// getModeInfo
// ============================================================
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
__dpmi_regs r;
*score = -1;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F01;
r.x.cx = mode;
r.x.es = __tb >> 4;
r.x.di = __tb & 0x0F;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
return;
}
// Read mode attributes
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
// Must have LFB support (bit 7)
if (!(attr & 0x0080)) {
return;
}
// Must be a graphics mode (bit 4)
if (!(attr & 0x0010)) {
return;
}
int32_t w = _farpeekw(_dos_ds, __tb + 18);
int32_t h = _farpeekw(_dos_ds, __tb + 20);
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
int32_t pitch = _farpeekw(_dos_ds, __tb + 16);
uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40);
// Must match or exceed requested resolution
if (w < requestedW || h < requestedH) {
return;
}
// Must be a supported bpp
if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) {
return;
}
// Score this mode
int32_t s = 0;
if (bpp == 16) {
s = 100;
} else if (bpp == 15) {
s = 90;
} else if (bpp == 32) {
s = 85;
} else if (bpp == 8) {
s = 70;
}
// Prefer the user's preferred bpp
if (bpp == preferredBpp) {
s += 20;
}
// Exact resolution match is preferred
if (w == requestedW && h == requestedH) {
s += 10;
} else {
s -= 10;
}
*score = s;
// Fill in display info
d->width = w;
d->height = h;
d->pitch = pitch;
d->format.bitsPerPixel = bpp;
d->format.bytesPerPixel = (bpp + 7) / 8;
// Read color masks from mode info
if (bpp >= 15) {
int32_t redSize = _farpeekb(_dos_ds, __tb + 31);
int32_t redPos = _farpeekb(_dos_ds, __tb + 32);
int32_t greenSize = _farpeekb(_dos_ds, __tb + 33);
int32_t greenPos = _farpeekb(_dos_ds, __tb + 34);
int32_t blueSize = _farpeekb(_dos_ds, __tb + 35);
int32_t bluePos = _farpeekb(_dos_ds, __tb + 36);
d->format.redBits = redSize;
d->format.redShift = redPos;
d->format.redMask = ((1U << redSize) - 1) << redPos;
d->format.greenBits = greenSize;
d->format.greenShift = greenPos;
d->format.greenMask = ((1U << greenSize) - 1) << greenPos;
d->format.blueBits = blueSize;
d->format.blueShift = bluePos;
d->format.blueMask = ((1U << blueSize) - 1) << bluePos;
}
// Store physical address in lfb field temporarily (will be remapped)
d->lfb = (uint8_t *)(uintptr_t)physAddr;
}
// ============================================================
// mapLfb
// ============================================================
static int32_t mapLfb(DisplayT *d, uint32_t physAddr) {
__dpmi_meminfo info;
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
info.address = physAddr;
info.size = fbSize;
if (__dpmi_physical_address_mapping(&info) != 0) {
fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr);
return -1;
}
// Lock the region to prevent paging
__dpmi_meminfo lockInfo;
lockInfo.address = info.address;
lockInfo.size = fbSize;
__dpmi_lock_linear_region(&lockInfo);
// Enable near pointers for direct access
if (__djgpp_nearptr_enable() == 0) {
fprintf(stderr, "VBE: Failed to enable near pointers\n");
return -1;
}
d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base);
return 0;
}
// ============================================================
// platformAltScanToChar
// ============================================================
char platformAltScanToChar(int32_t scancode) {
if (scancode < 0 || scancode > 255) {
return 0;
}
return sAltScanToAscii[scancode];
}
// ============================================================
// platformFlushRect
// ============================================================
void platformFlushRect(const DisplayT *d, const RectT *r) {
int32_t bpp = d->format.bytesPerPixel;
int32_t x = r->x;
int32_t y = r->y;
int32_t w = r->w;
int32_t h = r->h;
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
int32_t rowBytes = w * bpp;
int32_t pitch = d->pitch;
uint8_t *src = d->backBuf + y * pitch + x * bpp;
uint8_t *dst = d->lfb + y * pitch + x * bpp;
// Full-width flush: single large copy
if (rowBytes == pitch) {
int32_t totalBytes = pitch * h;
int32_t dwords = totalBytes >> 2;
int32_t remainder = totalBytes & 3;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(dwords)
:
: "memory"
);
while (remainder-- > 0) {
*dst++ = *src++;
}
} else {
// Partial scanlines — copy row by row with rep movsd
int32_t dwords = rowBytes >> 2;
int32_t remainder = rowBytes & 3;
for (int32_t i = 0; i < h; i++) {
int32_t dc = dwords;
uint8_t *s = src;
uint8_t *dd = dst;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dd), "+S"(s), "+c"(dc)
:
: "memory"
);
if (__builtin_expect(remainder > 0, 0)) {
int32_t rem = remainder;
while (rem-- > 0) {
*dd++ = *s++;
}
}
src += pitch;
dst += pitch;
}
}
}
// ============================================================
// platformInit
// ============================================================
void platformInit(void) {
// Disable Ctrl+C/Break termination
signal(SIGINT, SIG_IGN);
}
// ============================================================
// platformKeyboardGetModifiers
// ============================================================
int32_t platformKeyboardGetModifiers(void) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x1200;
__dpmi_int(0x16, &r);
return r.x.ax & 0xFF;
}
// ============================================================
// platformKeyboardRead
// ============================================================
bool platformKeyboardRead(PlatformKeyEventT *evt) {
__dpmi_regs r;
// Check if key is available (INT 16h, enhanced function 11h)
r.x.ax = 0x1100;
__dpmi_int(0x16, &r);
// Zero flag set = no key available
if (r.x.flags & 0x40) {
return false;
}
// Read the key (INT 16h, enhanced function 10h)
r.x.ax = 0x1000;
__dpmi_int(0x16, &r);
evt->scancode = (r.x.ax >> 8) & 0xFF;
evt->ascii = r.x.ax & 0xFF;
// Enhanced INT 16h returns ascii=0xE0 for grey/extended keys
// (arrows, Home, End, Insert, Delete, etc. on 101-key keyboards).
// Normalize to 0 so all extended key checks work uniformly.
if (evt->ascii == 0xE0) {
evt->ascii = 0;
}
return true;
}
// ============================================================
// platformMouseInit
// ============================================================
void platformMouseInit(int32_t screenW, int32_t screenH) {
__dpmi_regs r;
// Reset mouse driver
memset(&r, 0, sizeof(r));
r.x.ax = 0x0000;
__dpmi_int(0x33, &r);
// Set horizontal range
memset(&r, 0, sizeof(r));
r.x.ax = 0x0007;
r.x.cx = 0;
r.x.dx = screenW - 1;
__dpmi_int(0x33, &r);
// Set vertical range
memset(&r, 0, sizeof(r));
r.x.ax = 0x0008;
r.x.cx = 0;
r.x.dx = screenH - 1;
__dpmi_int(0x33, &r);
// Position cursor at center
memset(&r, 0, sizeof(r));
r.x.ax = 0x0004;
r.x.cx = screenW / 2;
r.x.dx = screenH / 2;
__dpmi_int(0x33, &r);
}
// ============================================================
// platformMousePoll
// ============================================================
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x33, &r);
*mx = r.x.cx;
*my = r.x.dx;
*buttons = r.x.bx;
}
// ============================================================
// platformSpanCopy8
// ============================================================
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
// Align to 4 bytes
while (((uintptr_t)dst & 3) && count > 0) {
*dst++ = *src++;
count--;
}
if (count >= 4) {
int32_t dwordCount = count >> 2;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(dwordCount)
:
: "memory"
);
dst += dwordCount * 4;
src += dwordCount * 4;
}
int32_t rem = count & 3;
while (rem-- > 0) {
*dst++ = *src++;
}
}
// ============================================================
// platformSpanCopy16
// ============================================================
void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
// Handle odd leading pixel for dword alignment
if (((uintptr_t)dst & 2) && count > 0) {
*(uint16_t *)dst = *(const uint16_t *)src;
dst += 2;
src += 2;
count--;
}
if (count >= 2) {
int32_t dwordCount = count >> 1;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(dwordCount)
:
: "memory"
);
dst += dwordCount * 4;
src += dwordCount * 4;
}
if (count & 1) {
*(uint16_t *)dst = *(const uint16_t *)src;
}
}
// ============================================================
// platformSpanCopy32
// ============================================================
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(count)
:
: "memory"
);
}
// ============================================================
// platformSpanFill8
// ============================================================
void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) {
uint8_t c = (uint8_t)color;
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
// Align to 4 bytes — skip if already aligned
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
while (((uintptr_t)dst & 3) && count > 0) {
*dst++ = c;
count--;
}
}
if (count >= 4) {
int32_t dwordCount = count >> 2;
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(dwordCount)
: "a"(dword)
: "memory"
);
dst += dwordCount * 4;
}
int32_t rem = count & 3;
while (rem-- > 0) {
*dst++ = c;
}
}
// ============================================================
// platformSpanFill16
// ============================================================
void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) {
uint16_t c = (uint16_t)color;
// Handle odd leading pixel for dword alignment
if (((uintptr_t)dst & 2) && count > 0) {
*(uint16_t *)dst = c;
dst += 2;
count--;
}
// Fill pairs of pixels as 32-bit dwords
if (count >= 2) {
uint32_t dword = ((uint32_t)c << 16) | c;
int32_t dwordCount = count >> 1;
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(dwordCount)
: "a"(dword)
: "memory"
);
dst += dwordCount * 4;
}
// Handle trailing odd pixel
if (count & 1) {
*(uint16_t *)dst = c;
}
}
// ============================================================
// platformSpanFill32
// ============================================================
void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(count)
: "a"(color)
: "memory"
);
}
// ============================================================
// platformValidateFilename — DOS 8.3 filename validation
// ============================================================
const char *platformValidateFilename(const char *name) {
static const char *reserved[] = {
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
NULL
};
if (!name || name[0] == '\0') {
return "Filename must not be empty.";
}
// Split into base and extension
const char *dot = strrchr(name, '.');
int32_t baseLen;
int32_t extLen;
if (dot) {
baseLen = (int32_t)(dot - name);
extLen = (int32_t)strlen(dot + 1);
} else {
baseLen = (int32_t)strlen(name);
extLen = 0;
}
if (baseLen < 1 || baseLen > 8) {
return "Filename must be 1-8 characters before the extension.";
}
if (extLen > 3) {
return "Extension must be 3 characters or fewer.";
}
// Check for invalid characters
for (const char *p = name; *p; p++) {
if (*p == '.') {
continue;
}
if (*p < '!' || *p > '~') {
return "Filename contains invalid characters.";
}
if (strchr(" \"*+,/:;<=>?[\\]|", *p)) {
return "Filename contains invalid characters.";
}
}
// Check for multiple dots
if (dot && strchr(name, '.') != dot) {
return "Filename may contain only one dot.";
}
// Check reserved device names (compare base only, case-insensitive)
char base[9];
int32_t copyLen = baseLen < 8 ? baseLen : 8;
for (int32_t i = 0; i < copyLen; i++) {
base[i] = toupper((unsigned char)name[i]);
}
base[copyLen] = '\0';
for (const char **r = reserved; *r; r++) {
if (strcmp(base, *r) == 0) {
return "That name is a reserved device name.";
}
}
return NULL;
}
// ============================================================
// platformVideoInit
// ============================================================
int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
uint16_t bestMode;
uint32_t physAddr;
memset(d, 0, sizeof(*d));
// Find the best VESA mode
if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) {
return -1;
}
// Save the physical address before we overwrite it
physAddr = (uint32_t)(uintptr_t)d->lfb;
// Set the mode
if (setVesaMode(bestMode) != 0) {
return -1;
}
// Map the LFB
if (mapLfb(d, physAddr) != 0) {
return -1;
}
// Allocate backbuffer
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
d->backBuf = (uint8_t *)malloc(fbSize);
if (!d->backBuf) {
fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize);
__djgpp_nearptr_disable();
return -1;
}
memset(d->backBuf, 0, fbSize);
// Set up palette for 8-bit mode
if (d->format.bitsPerPixel == 8) {
d->palette = (uint8_t *)malloc(768);
if (!d->palette) {
fprintf(stderr, "VBE: Failed to allocate palette\n");
free(d->backBuf);
d->backBuf = NULL;
__djgpp_nearptr_disable();
return -1;
}
dvxGeneratePalette(d->palette);
platformVideoSetPalette(d->palette, 0, 256);
}
// Initialize clip rect to full display
d->clipX = 0;
d->clipY = 0;
d->clipW = d->width;
d->clipH = d->height;
fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n",
bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch);
return 0;
}
// ============================================================
// platformVideoSetPalette
// ============================================================
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
// Set VGA DAC registers directly via port I/O
outportb(0x3C8, (uint8_t)firstEntry);
for (int32_t i = 0; i < count; i++) {
int32_t idx = (firstEntry + i) * 3;
outportb(0x3C9, pal[idx + 0] >> 2);
outportb(0x3C9, pal[idx + 1] >> 2);
outportb(0x3C9, pal[idx + 2] >> 2);
}
}
// ============================================================
// platformVideoShutdown
// ============================================================
void platformVideoShutdown(DisplayT *d) {
// Restore text mode (mode 3)
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x10, &r);
if (d->backBuf) {
free(d->backBuf);
d->backBuf = NULL;
}
if (d->palette) {
free(d->palette);
d->palette = NULL;
}
d->lfb = NULL;
__djgpp_nearptr_disable();
}
// ============================================================
// platformYield
// ============================================================
void platformYield(void) {
__dpmi_yield();
}
// ============================================================
// setVesaMode
// ============================================================
static int32_t setVesaMode(uint16_t mode) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F02;
r.x.bx = mode | 0x4000; // bit 14 = use LFB
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax);
return -1;
}
return 0;
}