wok diff emu2/stuff/video.c @ rev 25624

Add opendkim
author Pascal Bellard <pascal.bellard@slitaz.org>
date Wed Nov 08 16:45:57 2023 +0000 (6 months ago)
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/emu2/stuff/video.c	Wed Nov 08 16:45:57 2023 +0000
     1.3 @@ -0,0 +1,751 @@
     1.4 +#include "video.h"
     1.5 +#include "codepage.h"
     1.6 +#include "dbg.h"
     1.7 +#include "emu.h"
     1.8 +
     1.9 +#include <errno.h>
    1.10 +#include <fcntl.h>
    1.11 +#include <stdio.h>
    1.12 +#include <stdlib.h>
    1.13 +#include <string.h>
    1.14 +#include <sys/ioctl.h>
    1.15 +#include <termios.h>
    1.16 +#include <unistd.h>
    1.17 +
    1.18 +// Simulated character screen.
    1.19 +// This is a copy of the currently displayed output in the terminal window.
    1.20 +// We have a max of 128 rows of up to 256 columns, total of 32KB.
    1.21 +static uint16_t term_screen[64][256];
    1.22 +// Current line in output, lines bellow this are not currently displayed.
    1.23 +// This allows using only part of the terminal.
    1.24 +static int output_row;
    1.25 +// Current cursor row/column position in the terminal.
    1.26 +static unsigned term_posx, term_posy, term_color, term_cursor;
    1.27 +// Current terminal sizes
    1.28 +static unsigned term_sx, term_sy;
    1.29 +// Current emulated video sizes and cursor position
    1.30 +static unsigned vid_posx[8], vid_posy[8], vid_cursor;
    1.31 +static unsigned vid_sx, vid_sy, vid_color, vid_page;
    1.32 +static unsigned vid_font_lines, vid_no_blank;
    1.33 +// Signals that the terminal size needs updating
    1.34 +static volatile int term_needs_update;
    1.35 +// Terminal FD, allows video output even with redirection.
    1.36 +static FILE *tty_file;
    1.37 +// Video is already initialized
    1.38 +static int video_initialized;
    1.39 +
    1.40 +// Forward
    1.41 +static void term_goto_xy(unsigned x, unsigned y);
    1.42 +
    1.43 +// Signal handler - terminal size changed
    1.44 +// TODO: not used yet.
    1.45 +#if 0
    1.46 +static void sigwinch_handler(int sig)
    1.47 +{
    1.48 +    //    term_needs_upodate = 1;
    1.49 +}
    1.50 +#endif
    1.51 +
    1.52 +static void term_get_size(void)
    1.53 +{
    1.54 +    struct winsize ws;
    1.55 +    if(ioctl(fileno(tty_file), TIOCGWINSZ, &ws) != -1)
    1.56 +    {
    1.57 +        // TODO: perhaps restrict to "known" values
    1.58 +        term_sx = ws.ws_col;
    1.59 +        term_sy = ws.ws_row;
    1.60 +        if(term_sx < 40)
    1.61 +            term_sx = 40;
    1.62 +        else if(term_sx > 240)
    1.63 +            term_sx = 240;
    1.64 +        if(term_sy < 25)
    1.65 +            term_sy = 25;
    1.66 +        else if(term_sy > 64)
    1.67 +            term_sy = 64;
    1.68 +    }
    1.69 +    else
    1.70 +    {
    1.71 +        term_sx = 80;
    1.72 +        term_sy = 25;
    1.73 +    }
    1.74 +}
    1.75 +
    1.76 +// Update posx/posy in BIOS memory
    1.77 +static void update_posxy(void)
    1.78 +{
    1.79 +    int vid_size = vid_sy > 25 ? 0x20 : 0x10;
    1.80 +    memory[0x44C] = 0x00;
    1.81 +    memory[0x44D] = vid_size;
    1.82 +    memory[0x44E] = 0x00;
    1.83 +    memory[0x44F] = (vid_size * vid_page) & 0x7F;
    1.84 +    for(int i = 0; i < 8; i++)
    1.85 +    {
    1.86 +        memory[0x450 + i * 2] = vid_posx[i];
    1.87 +        memory[0x451 + i * 2] = vid_posy[i];
    1.88 +    }
    1.89 +    memory[0x462] = vid_page;
    1.90 +}
    1.91 +
    1.92 +// Clears the terminal data - not the actual terminal screen
    1.93 +static void clear_terminal(void)
    1.94 +{
    1.95 +    debug(debug_video, "clear terminal shadow\n");
    1.96 +    // Clear screen terminal:
    1.97 +    for(int y = 0; y < 64; y++)
    1.98 +        for(int x = 0; x < 256; x++)
    1.99 +            term_screen[y][x] = 0x0720;
   1.100 +    output_row = -1;
   1.101 +    term_posx = 0;
   1.102 +    term_posy = 0;
   1.103 +    // Get current terminal size
   1.104 +    term_get_size();
   1.105 +    putc('\r', tty_file); // Go to column 0
   1.106 +}
   1.107 +
   1.108 +static void set_text_mode(int clear, int sx, int sy)
   1.109 +{
   1.110 +    debug(debug_video, "set text mode%s\n", clear ? " and clear" : "");
   1.111 +    // Clear video screen
   1.112 +    if(clear)
   1.113 +    {
   1.114 +        uint16_t *vm = (uint16_t *)(memory + 0xB8000);
   1.115 +        for(int i = 0; i < 16384; i++)
   1.116 +            vm[i] = 0x0720;
   1.117 +    }
   1.118 +    for(int i = 0; i < 8; i++)
   1.119 +    {
   1.120 +        vid_posx[i] = 0;
   1.121 +        vid_posy[i] = 0;
   1.122 +    }
   1.123 +    vid_page = 0;
   1.124 +    vid_color = 0x07;
   1.125 +    vid_cursor = 1;
   1.126 +    // TODO: support other video modes
   1.127 +    vid_sx = sx;
   1.128 +    vid_sy = sy;
   1.129 +    vid_font_lines = 16;
   1.130 +    memory[0x449] = 0x03; // video mode
   1.131 +    memory[0x44A] = vid_sx;
   1.132 +    memory[0x484] = vid_sy - 1;
   1.133 +    update_posxy();
   1.134 +}
   1.135 +
   1.136 +static unsigned get_last_used_row(void)
   1.137 +{
   1.138 +    unsigned max = 0;
   1.139 +    for(unsigned y = 0; y < vid_sy; y++)
   1.140 +        for(unsigned x = 0; x < vid_sx; x++)
   1.141 +            if(term_screen[y][x] != 0x700 && term_screen[y][x] != 0x720)
   1.142 +                max = y + 1;
   1.143 +    return max;
   1.144 +}
   1.145 +
   1.146 +static void exit_video(void)
   1.147 +{
   1.148 +    vid_cursor = 1;
   1.149 +    check_screen();
   1.150 +    unsigned max = get_last_used_row();
   1.151 +    term_goto_xy(0, max);
   1.152 +    fputs("\x1b[m", tty_file);
   1.153 +    fclose(tty_file);
   1.154 +    debug(debug_video, "exit video - row %d\n", max);
   1.155 +}
   1.156 +
   1.157 +static void init_video(void)
   1.158 +{
   1.159 +    debug(debug_video, "starting video emulation.\n");
   1.160 +    int tty_fd = open("/dev/tty", O_NOCTTY | O_WRONLY);
   1.161 +    if(tty_fd < 0)
   1.162 +    {
   1.163 +        print_error("error at open TTY, %s\n", strerror(errno));
   1.164 +        exit(1);
   1.165 +    }
   1.166 +    tty_file = fdopen(tty_fd, "w");
   1.167 +    atexit(exit_video);
   1.168 +    video_initialized = 1;
   1.169 +
   1.170 +    // Fill the functionality table
   1.171 +    memory[0xC0100] = 0x08; // Only mode 3 supported
   1.172 +    memory[0xC0101] = 0x00;
   1.173 +    memory[0xC0102] = 0x00;
   1.174 +    memory[0xC0107] = 0x07; // Support 300, 350 and 400 scanlines
   1.175 +    memory[0xC0108] = 0x00; // Active character blocks?
   1.176 +    memory[0xC0109] = 0x00; // MAximum character blocks?
   1.177 +    memory[0xC0108] = 0xFF; // Support functions
   1.178 +
   1.179 +    // Set video mode
   1.180 +    set_text_mode(1, 80, 25);
   1.181 +    clear_terminal();
   1.182 +    term_needs_update = 0;
   1.183 +    term_cursor = 1;
   1.184 +    term_color = 0x07;
   1.185 +}
   1.186 +
   1.187 +int video_active(void)
   1.188 +{
   1.189 +    return video_initialized;
   1.190 +}
   1.191 +
   1.192 +static void set_color(uint8_t c)
   1.193 +{
   1.194 +    if(term_color != c)
   1.195 +    {
   1.196 +        static char cn[8] = "04261537";
   1.197 +        fprintf(tty_file, (c & 0x80) ? "\x1b[%c;9%c;10%cm" : "\x1b[%c;3%c;4%cm", (c & 0x08) ? '1' : '0', cn[c & 7],
   1.198 +                cn[(c >> 4) & 7]);
   1.199 +        term_color = c;
   1.200 +    }
   1.201 +}
   1.202 +
   1.203 +// Writes a DOS character to the current terminal position
   1.204 +static void put_vc(uint8_t c)
   1.205 +{
   1.206 +    uint16_t uc = get_unicode(c);
   1.207 +    if(uc < 128)
   1.208 +        putc(uc, tty_file);
   1.209 +    else if(uc < 0x800)
   1.210 +    {
   1.211 +        putc(0xC0 | (uc >> 6), tty_file);
   1.212 +        putc(0x80 | (uc & 0x3F), tty_file);
   1.213 +    }
   1.214 +    else
   1.215 +    {
   1.216 +        putc(0xE0 | (uc >> 12), tty_file);
   1.217 +        putc(0x80 | ((uc >> 6) & 0x3F), tty_file);
   1.218 +        putc(0x80 | (uc & 0x3F), tty_file);
   1.219 +    }
   1.220 +}
   1.221 +
   1.222 +// Move terminal cursor to the position
   1.223 +static void term_goto_xy(unsigned x, unsigned y)
   1.224 +{
   1.225 +    if(term_posy < y && (int)term_posy < output_row)
   1.226 +    {
   1.227 +        int inc = (int)y < output_row ? y - term_posy : output_row - term_posy;
   1.228 +        fprintf(tty_file, "\x1b[%dB", inc);
   1.229 +        term_posy += inc;
   1.230 +    }
   1.231 +    if(term_posy < y)
   1.232 +    {
   1.233 +        putc('\r', tty_file);
   1.234 +        // Set background color to black, as some terminals insert lines with
   1.235 +        // the current background color.
   1.236 +        set_color(term_color & 0x0F);
   1.237 +        // TODO: Draw new line with background color from video screen
   1.238 +        for(unsigned i = term_posy; i < y; i++)
   1.239 +            putc('\n', tty_file);
   1.240 +        term_posx = 0;
   1.241 +        term_posy = y;
   1.242 +    }
   1.243 +    if(term_posy > y)
   1.244 +    {
   1.245 +        fprintf(tty_file, "\x1b[%dA", term_posy - y);
   1.246 +        term_posy = y;
   1.247 +    }
   1.248 +    if(x == 0 && term_posx != 0)
   1.249 +    {
   1.250 +        putc('\r', tty_file);
   1.251 +        term_posx = 0;
   1.252 +    }
   1.253 +    if(term_posx < x)
   1.254 +    {
   1.255 +        fprintf(tty_file, "\x1b[%dC", x - term_posx);
   1.256 +        term_posx = x;
   1.257 +    }
   1.258 +    if(term_posx > x)
   1.259 +    {
   1.260 +        fprintf(tty_file, "\x1b[%dD", term_posx - x);
   1.261 +        term_posx = x;
   1.262 +    }
   1.263 +}
   1.264 +
   1.265 +// Outputs a character with the given attributes at the given position
   1.266 +static void put_vc_xy(uint8_t vc, uint8_t color, unsigned x, unsigned y)
   1.267 +{
   1.268 +    term_goto_xy(x, y);
   1.269 +    set_color(color);
   1.270 +
   1.271 +    put_vc(vc);
   1.272 +    term_posx++;
   1.273 +    if(term_posx >= term_sx)
   1.274 +    {
   1.275 +        term_posx = 0;
   1.276 +        term_posy++;
   1.277 +    }
   1.278 +
   1.279 +    if(output_row < (int)term_posy)
   1.280 +        output_row = term_posy;
   1.281 +}
   1.282 +
   1.283 +// Compares current screen with memory data
   1.284 +void check_screen(void)
   1.285 +{
   1.286 +    // Exit if not in video mode
   1.287 +    if(!video_initialized)
   1.288 +        return;
   1.289 +
   1.290 +    uint16_t memp = (vid_page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
   1.291 +    uint16_t *vm = (uint16_t *)(memory + 0xB8000 + memp);
   1.292 +    unsigned max = output_row + 1;
   1.293 +    for(unsigned y = output_row + 1; y < vid_sy; y++)
   1.294 +        for(unsigned x = 0; x < vid_sx; x++)
   1.295 +            if(vm[x + y * vid_sx] != term_screen[y][x])
   1.296 +                max = y + 1;
   1.297 +
   1.298 +    for(unsigned y = 0; y < max; y++)
   1.299 +        for(unsigned x = 0; x < vid_sx; x++)
   1.300 +        {
   1.301 +            int16_t vc = vm[x + y * vid_sx];
   1.302 +            if(vc != term_screen[y][x])
   1.303 +            {
   1.304 +                // Output character
   1.305 +                term_screen[y][x] = vc;
   1.306 +                put_vc_xy(vc & 0xFF, vc >> 8, x, y);
   1.307 +            }
   1.308 +        }
   1.309 +    if(term_cursor != vid_cursor)
   1.310 +    {
   1.311 +        term_cursor = vid_cursor;
   1.312 +        if(vid_cursor)
   1.313 +            fputs("\x1b[?25h", tty_file);
   1.314 +        else
   1.315 +            fputs("\x1b[?25l", tty_file);
   1.316 +    }
   1.317 +    if(term_cursor)
   1.318 +        // Move cursor
   1.319 +        term_goto_xy(vid_posx[vid_page], vid_posy[vid_page]);
   1.320 +    fflush(tty_file);
   1.321 +}
   1.322 +
   1.323 +static void vid_scroll_up(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, int n,
   1.324 +                          int page)
   1.325 +{
   1.326 +    debug(debug_video, "scroll up %d: (%d, %d) - (%d, %d)\n", n, x0, y0, x1, y1);
   1.327 +
   1.328 +    // Check parameters
   1.329 +    if(x1 >= vid_sx)
   1.330 +        x1 = vid_sx - 1;
   1.331 +    if(y1 >= vid_sy)
   1.332 +        y1 = vid_sy - 1;
   1.333 +    if(y0 > y1 || x0 > x1)
   1.334 +        return;
   1.335 +    if(n > y1 - y0 + 1 || !n)
   1.336 +        n = y1 + 1 - y0;
   1.337 +
   1.338 +    // Scroll TERMINAL if we are scrolling (almost) the entire screen
   1.339 +    if(y0 == 0 && y1 >= vid_sy - 2 && x0 < 2 && x1 >= vid_sx - 2)
   1.340 +    {
   1.341 +        // Update screen before
   1.342 +        check_screen();
   1.343 +        int m = n > output_row + 1 ? output_row + 1 : n;
   1.344 +        if(term_posy < m)
   1.345 +            term_goto_xy(0, m);
   1.346 +        output_row -= m;
   1.347 +        term_posy -= m;
   1.348 +        for(unsigned y = 0; y + m < term_sy; y++)
   1.349 +            for(unsigned x = 0; x < term_sx; x++)
   1.350 +                term_screen[y][x] = term_screen[y + m][x];
   1.351 +        for(unsigned y = term_sy - m; y < term_sy; y++)
   1.352 +            for(unsigned x = 0; x < term_sx; x++)
   1.353 +                term_screen[y][x] = 0x0720;
   1.354 +    }
   1.355 +
   1.356 +    // Scroll VIDEO
   1.357 +    uint16_t memp = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
   1.358 +    uint16_t *vm = (uint16_t *)(memory + 0xB8000 + memp);
   1.359 +    for(unsigned y = y0; y + n <= y1; y++)
   1.360 +        for(unsigned x = x0; x <= x1; x++)
   1.361 +            vm[x + y * vid_sx] = vm[x + y * vid_sx + n * vid_sx];
   1.362 +    // Set last rows
   1.363 +    for(unsigned y = y1 - (n - 1); y <= y1; y++)
   1.364 +        for(unsigned x = x0; x <= x1; x++)
   1.365 +            vm[x + y * vid_sx] = (vid_color << 8) + 0x20;
   1.366 +}
   1.367 +
   1.368 +static void vid_scroll_dwn(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, unsigned n,
   1.369 +                           int page)
   1.370 +{
   1.371 +    debug(debug_video, "scroll down %d: (%d, %d) - (%d, %d)\n", n, x0, y0, x1, y1);
   1.372 +
   1.373 +    // Check parameters
   1.374 +    if(x1 >= vid_sx)
   1.375 +        x1 = vid_sx - 1;
   1.376 +    if(y1 >= vid_sy)
   1.377 +        y1 = vid_sy - 1;
   1.378 +    if(y0 > y1 || x0 > x1)
   1.379 +        return;
   1.380 +    if(n > y1 - y0 + 1 || !n)
   1.381 +        n = y1 + 1 - y0;
   1.382 +
   1.383 +    // TODO: try to scroll TERMINAL
   1.384 +
   1.385 +    // Scroll VIDEO
   1.386 +    uint16_t memp = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
   1.387 +    uint16_t *vm = (uint16_t *)(memory + 0xB8000 + memp);
   1.388 +    for(unsigned y = y1; y >= y0 + n; y--)
   1.389 +        for(unsigned x = x0; x <= x1; x++)
   1.390 +            vm[x + y * vid_sx] = vm[x + y * vid_sx - n * vid_sx];
   1.391 +    // Set first rows
   1.392 +    for(unsigned y = y0; y < y0 + n; y++)
   1.393 +        for(unsigned x = x0; x <= x1; x++)
   1.394 +            vm[x + y * vid_sx] = (vid_color << 8) + 0x20;
   1.395 +}
   1.396 +
   1.397 +static void set_xy(unsigned x, unsigned y, uint16_t c, uint16_t mask, int page)
   1.398 +{
   1.399 +    uint16_t mem = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
   1.400 +    uint16_t *vm = (uint16_t *)(memory + 0xB8000 + mem);
   1.401 +    vm[x + y * vid_sx] = (vm[x + y * vid_sx] & mask) | c;
   1.402 +}
   1.403 +
   1.404 +static uint16_t get_xy(unsigned x, unsigned y, int page)
   1.405 +{
   1.406 +    uint16_t mem = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
   1.407 +    uint16_t *vm = (uint16_t *)(memory + 0xB8000 + mem);
   1.408 +    return vm[x + y * vid_sx];
   1.409 +}
   1.410 +
   1.411 +static void video_putchar(uint8_t ch, uint16_t at, int page)
   1.412 +{
   1.413 +    page = page & 7;
   1.414 +    if(ch == 0x0A)
   1.415 +    {
   1.416 +        vid_posy[page]++;
   1.417 +        while(vid_posy[page] >= vid_sy)
   1.418 +        {
   1.419 +            vid_posy[page] = vid_sy - 1;
   1.420 +            vid_scroll_up(0, 0, vid_sx - 1, vid_sy - 1, 1, page);
   1.421 +        }
   1.422 +    }
   1.423 +    else if(ch == 0x0D)
   1.424 +        vid_posx[page] = 0;
   1.425 +    else if(ch == 0x08)
   1.426 +    {
   1.427 +        if(vid_posx[page] > 0)
   1.428 +            vid_posx[page]--;
   1.429 +    }
   1.430 +    else
   1.431 +    {
   1.432 +        if(at & 0xFFFF)
   1.433 +            set_xy(vid_posx[page], vid_posy[page], ch, 0xFF00, page);
   1.434 +        else
   1.435 +            set_xy(vid_posx[page], vid_posy[page], ch + (at << 8), 0, page);
   1.436 +        vid_posx[page]++;
   1.437 +        if(vid_posx[page] >= vid_sx)
   1.438 +        {
   1.439 +            vid_posx[page] = 0;
   1.440 +            vid_posy[page]++;
   1.441 +            while(vid_posy[page] >= vid_sy)
   1.442 +            {
   1.443 +                vid_posy[page] = vid_sy - 1;
   1.444 +                vid_scroll_up(0, 0, vid_sx - 1, vid_sy - 1, 1, page);
   1.445 +            }
   1.446 +        }
   1.447 +    }
   1.448 +    update_posxy();
   1.449 +}
   1.450 +
   1.451 +void video_putch(char ch)
   1.452 +{
   1.453 +    debug(debug_video, "putchar %02x at (%d,%d)\n", ch & 0xFF, vid_posx[vid_page],
   1.454 +          vid_posy[vid_page]);
   1.455 +    video_putchar(ch, 0xFF00, vid_page);
   1.456 +}
   1.457 +
   1.458 +// VIDEO int
   1.459 +void int10()
   1.460 +{
   1.461 +    debug(debug_int, "V-10%04X: BX=%04X\n", cpuGetAX(), cpuGetBX());
   1.462 +    debug(debug_video, "V-10%04X: BX=%04X\n", cpuGetAX(), cpuGetBX());
   1.463 +    if(!video_initialized)
   1.464 +        init_video();
   1.465 +    unsigned ax = cpuGetAX();
   1.466 +    switch(ax >> 8)
   1.467 +    {
   1.468 +    case 0x00: // SET VIDEO MODE
   1.469 +        if((ax & 0x7F) > 3)
   1.470 +            debug(debug_video, "-> SET GRAPHICS MODE %x<-\n", ax & 0xFF);
   1.471 +        else
   1.472 +        {
   1.473 +            set_text_mode((ax & 0x80) == 0, (ax < 2) ? 40 : 80, 25);
   1.474 +            vid_no_blank = ax & 0x80;
   1.475 +        }
   1.476 +        break;
   1.477 +    case 0x01:                              // SET CURSOR SHAPE
   1.478 +        if((cpuGetCX() & 0x6000) == 0x2000) // Hide cursor
   1.479 +            vid_cursor = 0;
   1.480 +        else
   1.481 +            vid_cursor = 1;
   1.482 +        break;
   1.483 +    case 0x02: // SET CURSOR POS
   1.484 +    {
   1.485 +        int page = (cpuGetBX() >> 8) & 7;
   1.486 +        vid_posx[page] = cpuGetDX() & 0xFF;
   1.487 +        vid_posy[page] = cpuGetDX() >> 8;
   1.488 +        if(vid_posx[page] >= vid_sx)
   1.489 +            vid_posx[page] = vid_sx - 1;
   1.490 +        if(vid_posy[page] >= vid_sy)
   1.491 +            vid_posy[page] = vid_sy - 1;
   1.492 +        update_posxy();
   1.493 +        break;
   1.494 +    }
   1.495 +    case 0x03: // GET CURSOR POS
   1.496 +    {
   1.497 +        int page = (cpuGetBX() >> 8) & 7;
   1.498 +        cpuSetDX(vid_posx[page] + (vid_posy[page] << 8));
   1.499 +        cpuSetCX(0x0010);
   1.500 +        break;
   1.501 +    }
   1.502 +    case 0x05: // SELECT DISPLAY PAGE
   1.503 +        if((ax & 0xFF) > 7)
   1.504 +            debug(debug_video, "WARN: Select display page > 7!\n");
   1.505 +        else
   1.506 +        {
   1.507 +            vid_page = ax & 7;
   1.508 +            update_posxy();
   1.509 +        }
   1.510 +        break;
   1.511 +    case 0x06: // SCROLL UP WINDOW
   1.512 +    {
   1.513 +        uint16_t cx = cpuGetCX(), dx = cpuGetDX();
   1.514 +        vid_color = cpuGetBX() >> 8;
   1.515 +        vid_scroll_up(cx, cx >> 8, dx, dx >> 8, ax & 0xFF, vid_page);
   1.516 +        break;
   1.517 +    }
   1.518 +    case 0x07: // SCROLL DOWN WINDOW
   1.519 +    {
   1.520 +        uint16_t cx = cpuGetCX(), dx = cpuGetDX();
   1.521 +        vid_color = cpuGetBX() >> 8;
   1.522 +        vid_scroll_dwn(cx, cx >> 8, dx, dx >> 8, ax & 0xFF, vid_page);
   1.523 +        break;
   1.524 +    }
   1.525 +    case 0x08: // READ CHAR AT CURSOR
   1.526 +    {
   1.527 +        int page = (cpuGetBX() >> 8) & 7;
   1.528 +        cpuSetAX(get_xy(vid_posx[page], vid_posy[page], page));
   1.529 +        break;
   1.530 +    }
   1.531 +    case 0x09: // WRITE CHAR AT CURSOR
   1.532 +    case 0x0A: // WRITE CHAR ONLY AT CURSOR
   1.533 +    {
   1.534 +        int page = (cpuGetBX() >> 8) & 7;
   1.535 +        uint16_t px = vid_posx[page];
   1.536 +        uint16_t py = vid_posy[page];
   1.537 +        uint16_t mask = (ax & 0x0100) ? 0 : 0xFF00;
   1.538 +        uint16_t ch = ((ax & 0xFF) | (cpuGetBX() << 8)) & ~mask;
   1.539 +        for(int i = cpuGetCX(); i > 0; i--)
   1.540 +        {
   1.541 +            set_xy(px, py, ch, mask, page);
   1.542 +            px++;
   1.543 +            if(px >= vid_sx)
   1.544 +            {
   1.545 +                px = 0;
   1.546 +                py++;
   1.547 +                if(py >= vid_sy)
   1.548 +                    py = 0;
   1.549 +            }
   1.550 +        }
   1.551 +        break;
   1.552 +    }
   1.553 +    case 0x0E: // TELETYPE OUTPUT
   1.554 +        video_putchar(ax, 0xFF00, (cpuGetBX() >> 8) & 7);
   1.555 +        break;
   1.556 +    case 0x0F: // GET CURRENT VIDEO MODE
   1.557 +        cpuSetAX((vid_sx << 8) | 0x0003 | vid_no_blank); // 80x25 mode
   1.558 +        cpuSetBX((vid_page << 8) | (0xFF & cpuGetBX()));
   1.559 +        break;
   1.560 +    case 0x10:
   1.561 +        if(ax == 0x1002) // TODO: Set pallete registers - ignore
   1.562 +            break;
   1.563 +        else if(ax == 0x1003) // TODO: Set blinking state
   1.564 +            break;
   1.565 +        debug(debug_video, "UNHANDLED INT 10, AX=%04x\n", ax);
   1.566 +        break;
   1.567 +    case 0x11:
   1.568 +        if(ax == 0x1130)
   1.569 +        {
   1.570 +            cpuSetDX((vid_sy - 1) & 0xFF);
   1.571 +            cpuSetCX(vid_font_lines);
   1.572 +        }
   1.573 +        else if(ax == 0x1104 || ax == 0x1111 || ax == 0x1114)
   1.574 +        {
   1.575 +            // Clear end-of-screen
   1.576 +            unsigned max = get_last_used_row();
   1.577 +            debug(debug_video, "set 25 lines mode %d\n", max);
   1.578 +            if(max > 25)
   1.579 +            {
   1.580 +                term_goto_xy(0, 24);
   1.581 +                set_color(0x07);
   1.582 +                fputs("\x1b[J", tty_file);
   1.583 +                for(int y = 25; y < 64; y++)
   1.584 +                    for(int x = 0; x < 256; x++)
   1.585 +                        term_screen[y][x] = 0x0720;
   1.586 +                if(output_row > 24)
   1.587 +                    output_row = 24;
   1.588 +            }
   1.589 +            // Set 8x16 font - 80x25 mode:
   1.590 +            vid_sy = 25;
   1.591 +            vid_font_lines = 16;
   1.592 +            memory[0x484] = vid_sy - 1;
   1.593 +            update_posxy();
   1.594 +        }
   1.595 +        else if(ax == 0x1102 || ax == 0x1112)
   1.596 +        {
   1.597 +            // Set 8x8 font - 80x43 or 80x50 mode:
   1.598 +            debug(debug_video, "set 43/50 lines mode\n");
   1.599 +            // Hack - QBASIC.EXE assumes that the mode is always 50 lines on VGA,
   1.600 +            // and *sets* the height into the BIOS area!
   1.601 +            if(memory[0x484] > 42)
   1.602 +                vid_sy = 50;
   1.603 +            else
   1.604 +                vid_sy = 43;
   1.605 +            vid_font_lines = 8;
   1.606 +            memory[0x484] = vid_sy - 1;
   1.607 +            update_posxy();
   1.608 +        }
   1.609 +        break;
   1.610 +    case 0x12: // ALT FUNCTION SELECT
   1.611 +    {
   1.612 +        int bl = cpuGetBX() & 0xFF;
   1.613 +        if(bl == 0x10) // GET EGA INFO
   1.614 +        {
   1.615 +            cpuSetBX(0x0003);
   1.616 +            cpuSetCX(0x0000);
   1.617 +            cpuSetAX(0);
   1.618 +        }
   1.619 +        else if(bl == 0x30) // SET VERTICAL RESOLUTION
   1.620 +        {
   1.621 +            // TODO: select 25/28 lines
   1.622 +            cpuSetAX(0x1212);
   1.623 +        }
   1.624 +        else
   1.625 +            debug(debug_video, "UNHANDLED INT 10, AH=12 BL=%02x\n", bl);
   1.626 +    }
   1.627 +    break;
   1.628 +    case 0x13: // WRITE STRING
   1.629 +    {
   1.630 +        int page = (cpuGetBX() >> 8) & 7;
   1.631 +        vid_posx[page] = cpuGetDX() & 0xFF;
   1.632 +        vid_posy[page] = cpuGetDX() >> 8;
   1.633 +        if(vid_posx[page] >= vid_sx)
   1.634 +            vid_posx[page] = vid_sx - 1;
   1.635 +        if(vid_posy[page] >= vid_sy)
   1.636 +            vid_posy[page] = vid_sy - 1;
   1.637 +        int save_posx = vid_posx[page];
   1.638 +        int save_posy = vid_posy[page];
   1.639 +        int addr = cpuGetAddrES(cpuGetBP());
   1.640 +        int cnt = cpuGetCX();
   1.641 +        if(ax & 2)
   1.642 +        {
   1.643 +            while(cnt && addr < 0xFFFFF)
   1.644 +            {
   1.645 +                video_putchar(memory[addr], memory[addr + 1], page);
   1.646 +                addr += 2;
   1.647 +                cnt--;
   1.648 +            }
   1.649 +        }
   1.650 +        else
   1.651 +        {
   1.652 +            uint8_t at = cpuGetBX() >> 8;
   1.653 +            while(cnt && addr <= 0xFFFFF)
   1.654 +            {
   1.655 +                video_putchar(memory[addr], at, page);
   1.656 +                addr++;
   1.657 +                cnt--;
   1.658 +            }
   1.659 +        }
   1.660 +        if(!(ax & 1))
   1.661 +        {
   1.662 +            vid_posx[page] = save_posx;
   1.663 +            vid_posy[page] = save_posy;
   1.664 +        }
   1.665 +        update_posxy();
   1.666 +    }
   1.667 +    break;
   1.668 +    case 0x1A: // GET/SET DISPLAY COMBINATION CODE
   1.669 +        cpuSetAX(0x001A);
   1.670 +        cpuSetBX(0x0008); // VGA + analog color display
   1.671 +        break;
   1.672 +    case 0x1B: // STATE INFO
   1.673 +        if(cpuGetBX() == 0x0000)
   1.674 +        {
   1.675 +            int addr = cpuGetAddrES(cpuGetDI());
   1.676 +            if(addr < 0xFFF00)
   1.677 +            {
   1.678 +                // Store state information
   1.679 +                memset(memory + addr, 0, 64);
   1.680 +                memory[addr + 0] = 0x00;
   1.681 +                memory[addr + 1] = 0x01;
   1.682 +                memory[addr + 2] = 0x00;
   1.683 +                memory[addr + 3] = 0xC0; // static-func table at C000:0000
   1.684 +                memory[addr + 4] = 0x03; // Video mode
   1.685 +                memory[addr + 5] = vid_sx;
   1.686 +                memory[addr + 6] = vid_sx >> 8;
   1.687 +                for(int i = 0; i < 8; i++)
   1.688 +                {
   1.689 +                    memory[addr + 11 + i * 2] = vid_posx[i];
   1.690 +                    memory[addr + 12 + i * 2] = vid_posy[i];
   1.691 +                }
   1.692 +                memory[addr + 27] = vid_cursor * 6; // cursor start scanline
   1.693 +                memory[addr + 28] = vid_cursor * 7; // cursor end scanline
   1.694 +                memory[addr + 29] = 0;              // current page
   1.695 +                memory[addr + 30] = 0xD4;
   1.696 +                memory[addr + 31] = 0x03; // CRTC port: 03D4
   1.697 +                memory[addr + 34] = vid_sy;
   1.698 +                memory[addr + 35] = vid_font_lines;
   1.699 +                memory[addr + 36] = 0x00; // font lines: 0010
   1.700 +                memory[addr + 39] = 0x10;
   1.701 +                memory[addr + 40] = 0x00; // # of colors: 0010
   1.702 +                memory[addr + 41] = vid_sy > 25 ? 4 : 8; // # of pages
   1.703 +                memory[addr + 42] = 2;    // # of scan-lines - get from vid_sy
   1.704 +                memory[addr + 49] = 3;    // 256k memory
   1.705 +                cpuSetAX(0x1B1B);
   1.706 +            }
   1.707 +        }
   1.708 +        break;
   1.709 +    case 0xEF: // TEST MSHERC.COM DISPLAY TYPE
   1.710 +        // Ignored
   1.711 +        break;
   1.712 +    default:
   1.713 +        debug(debug_video, "UNHANDLED INT 10, AX=%04x\n", ax);
   1.714 +    }
   1.715 +}
   1.716 +
   1.717 +// CRTC port emulation, some software use it to fix "snow" in CGA modes.
   1.718 +static uint8_t crtc_port;
   1.719 +static uint16_t crtc_cursor_loc;
   1.720 +
   1.721 +uint8_t video_crtc_read(int port)
   1.722 +{
   1.723 +    if(port & 1)
   1.724 +    {
   1.725 +        if(crtc_port == 0x0E)
   1.726 +            return crtc_cursor_loc >> 8;
   1.727 +        if(crtc_port == 0x0F)
   1.728 +            return crtc_cursor_loc;
   1.729 +        else
   1.730 +            return 0;
   1.731 +    }
   1.732 +    else
   1.733 +        return crtc_port;
   1.734 +}
   1.735 +
   1.736 +void video_crtc_write(int port, uint8_t value)
   1.737 +{
   1.738 +    if(port & 1)
   1.739 +    {
   1.740 +        if(crtc_port == 0x0E)
   1.741 +            crtc_cursor_loc = (crtc_cursor_loc & 0xFF) | (value << 8);
   1.742 +        if(crtc_port == 0x0F)
   1.743 +            crtc_cursor_loc = (crtc_cursor_loc & 0xFF00) | (value);
   1.744 +        else
   1.745 +            debug(debug_video, "CRTC port write [%02x] <- %02x\n", crtc_port, value);
   1.746 +    }
   1.747 +    else
   1.748 +        crtc_port = value;
   1.749 +}
   1.750 +
   1.751 +int video_get_col(void)
   1.752 +{
   1.753 +    return vid_posx[vid_page];
   1.754 +}