rev |
line source |
pascal@25624
|
1 #include "video.h"
|
pascal@25624
|
2 #include "codepage.h"
|
pascal@25624
|
3 #include "dbg.h"
|
pascal@25624
|
4 #include "emu.h"
|
pascal@25624
|
5
|
pascal@25624
|
6 #include <errno.h>
|
pascal@25624
|
7 #include <fcntl.h>
|
pascal@25624
|
8 #include <stdio.h>
|
pascal@25624
|
9 #include <stdlib.h>
|
pascal@25624
|
10 #include <string.h>
|
pascal@25624
|
11 #include <sys/ioctl.h>
|
pascal@25624
|
12 #include <termios.h>
|
pascal@25624
|
13 #include <unistd.h>
|
pascal@25624
|
14
|
pascal@25624
|
15 // Simulated character screen.
|
pascal@25624
|
16 // This is a copy of the currently displayed output in the terminal window.
|
pascal@25624
|
17 // We have a max of 128 rows of up to 256 columns, total of 32KB.
|
pascal@25624
|
18 static uint16_t term_screen[64][256];
|
pascal@25624
|
19 // Current line in output, lines bellow this are not currently displayed.
|
pascal@25624
|
20 // This allows using only part of the terminal.
|
pascal@25624
|
21 static int output_row;
|
pascal@25624
|
22 // Current cursor row/column position in the terminal.
|
pascal@25624
|
23 static unsigned term_posx, term_posy, term_color, term_cursor;
|
pascal@25624
|
24 // Current terminal sizes
|
pascal@25624
|
25 static unsigned term_sx, term_sy;
|
pascal@25624
|
26 // Current emulated video sizes and cursor position
|
pascal@25624
|
27 static unsigned vid_posx[8], vid_posy[8], vid_cursor;
|
pascal@25624
|
28 static unsigned vid_sx, vid_sy, vid_color, vid_page;
|
pascal@25624
|
29 static unsigned vid_font_lines, vid_no_blank;
|
pascal@25624
|
30 // Signals that the terminal size needs updating
|
pascal@25624
|
31 static volatile int term_needs_update;
|
pascal@25624
|
32 // Terminal FD, allows video output even with redirection.
|
pascal@25624
|
33 static FILE *tty_file;
|
pascal@25624
|
34 // Video is already initialized
|
pascal@25624
|
35 static int video_initialized;
|
pascal@25624
|
36
|
pascal@25624
|
37 // Forward
|
pascal@25624
|
38 static void term_goto_xy(unsigned x, unsigned y);
|
pascal@25624
|
39
|
pascal@25624
|
40 // Signal handler - terminal size changed
|
pascal@25624
|
41 // TODO: not used yet.
|
pascal@25624
|
42 #if 0
|
pascal@25624
|
43 static void sigwinch_handler(int sig)
|
pascal@25624
|
44 {
|
pascal@25624
|
45 // term_needs_upodate = 1;
|
pascal@25624
|
46 }
|
pascal@25624
|
47 #endif
|
pascal@25624
|
48
|
pascal@25624
|
49 static void term_get_size(void)
|
pascal@25624
|
50 {
|
pascal@25624
|
51 struct winsize ws;
|
pascal@25624
|
52 if(ioctl(fileno(tty_file), TIOCGWINSZ, &ws) != -1)
|
pascal@25624
|
53 {
|
pascal@25624
|
54 // TODO: perhaps restrict to "known" values
|
pascal@25624
|
55 term_sx = ws.ws_col;
|
pascal@25624
|
56 term_sy = ws.ws_row;
|
pascal@25624
|
57 if(term_sx < 40)
|
pascal@25624
|
58 term_sx = 40;
|
pascal@25624
|
59 else if(term_sx > 240)
|
pascal@25624
|
60 term_sx = 240;
|
pascal@25624
|
61 if(term_sy < 25)
|
pascal@25624
|
62 term_sy = 25;
|
pascal@25624
|
63 else if(term_sy > 64)
|
pascal@25624
|
64 term_sy = 64;
|
pascal@25624
|
65 }
|
pascal@25624
|
66 else
|
pascal@25624
|
67 {
|
pascal@25624
|
68 term_sx = 80;
|
pascal@25624
|
69 term_sy = 25;
|
pascal@25624
|
70 }
|
pascal@25624
|
71 }
|
pascal@25624
|
72
|
pascal@25624
|
73 // Update posx/posy in BIOS memory
|
pascal@25624
|
74 static void update_posxy(void)
|
pascal@25624
|
75 {
|
pascal@25624
|
76 int vid_size = vid_sy > 25 ? 0x20 : 0x10;
|
pascal@25624
|
77 memory[0x44C] = 0x00;
|
pascal@25624
|
78 memory[0x44D] = vid_size;
|
pascal@25624
|
79 memory[0x44E] = 0x00;
|
pascal@25624
|
80 memory[0x44F] = (vid_size * vid_page) & 0x7F;
|
pascal@25624
|
81 for(int i = 0; i < 8; i++)
|
pascal@25624
|
82 {
|
pascal@25624
|
83 memory[0x450 + i * 2] = vid_posx[i];
|
pascal@25624
|
84 memory[0x451 + i * 2] = vid_posy[i];
|
pascal@25624
|
85 }
|
pascal@25624
|
86 memory[0x462] = vid_page;
|
pascal@25624
|
87 }
|
pascal@25624
|
88
|
pascal@25624
|
89 // Clears the terminal data - not the actual terminal screen
|
pascal@25624
|
90 static void clear_terminal(void)
|
pascal@25624
|
91 {
|
pascal@25624
|
92 debug(debug_video, "clear terminal shadow\n");
|
pascal@25624
|
93 // Clear screen terminal:
|
pascal@25624
|
94 for(int y = 0; y < 64; y++)
|
pascal@25624
|
95 for(int x = 0; x < 256; x++)
|
pascal@25624
|
96 term_screen[y][x] = 0x0720;
|
pascal@25624
|
97 output_row = -1;
|
pascal@25624
|
98 term_posx = 0;
|
pascal@25624
|
99 term_posy = 0;
|
pascal@25624
|
100 // Get current terminal size
|
pascal@25624
|
101 term_get_size();
|
pascal@25624
|
102 putc('\r', tty_file); // Go to column 0
|
pascal@25624
|
103 }
|
pascal@25624
|
104
|
pascal@25624
|
105 static void set_text_mode(int clear, int sx, int sy)
|
pascal@25624
|
106 {
|
pascal@25624
|
107 debug(debug_video, "set text mode%s\n", clear ? " and clear" : "");
|
pascal@25624
|
108 // Clear video screen
|
pascal@25624
|
109 if(clear)
|
pascal@25624
|
110 {
|
pascal@25624
|
111 uint16_t *vm = (uint16_t *)(memory + 0xB8000);
|
pascal@25624
|
112 for(int i = 0; i < 16384; i++)
|
pascal@25624
|
113 vm[i] = 0x0720;
|
pascal@25624
|
114 }
|
pascal@25624
|
115 for(int i = 0; i < 8; i++)
|
pascal@25624
|
116 {
|
pascal@25624
|
117 vid_posx[i] = 0;
|
pascal@25624
|
118 vid_posy[i] = 0;
|
pascal@25624
|
119 }
|
pascal@25624
|
120 vid_page = 0;
|
pascal@25624
|
121 vid_color = 0x07;
|
pascal@25624
|
122 vid_cursor = 1;
|
pascal@25624
|
123 // TODO: support other video modes
|
pascal@25624
|
124 vid_sx = sx;
|
pascal@25624
|
125 vid_sy = sy;
|
pascal@25624
|
126 vid_font_lines = 16;
|
pascal@25624
|
127 memory[0x449] = 0x03; // video mode
|
pascal@25624
|
128 memory[0x44A] = vid_sx;
|
pascal@25624
|
129 memory[0x484] = vid_sy - 1;
|
pascal@25624
|
130 update_posxy();
|
pascal@25624
|
131 }
|
pascal@25624
|
132
|
pascal@25624
|
133 static unsigned get_last_used_row(void)
|
pascal@25624
|
134 {
|
pascal@25624
|
135 unsigned max = 0;
|
pascal@25624
|
136 for(unsigned y = 0; y < vid_sy; y++)
|
pascal@25624
|
137 for(unsigned x = 0; x < vid_sx; x++)
|
pascal@25624
|
138 if(term_screen[y][x] != 0x700 && term_screen[y][x] != 0x720)
|
pascal@25624
|
139 max = y + 1;
|
pascal@25624
|
140 return max;
|
pascal@25624
|
141 }
|
pascal@25624
|
142
|
pascal@25624
|
143 static void exit_video(void)
|
pascal@25624
|
144 {
|
pascal@25624
|
145 vid_cursor = 1;
|
pascal@25624
|
146 check_screen();
|
pascal@25624
|
147 unsigned max = get_last_used_row();
|
pascal@25624
|
148 term_goto_xy(0, max);
|
pascal@25624
|
149 fputs("\x1b[m", tty_file);
|
pascal@25624
|
150 fclose(tty_file);
|
pascal@25624
|
151 debug(debug_video, "exit video - row %d\n", max);
|
pascal@25624
|
152 }
|
pascal@25624
|
153
|
pascal@25624
|
154 static void init_video(void)
|
pascal@25624
|
155 {
|
pascal@25624
|
156 debug(debug_video, "starting video emulation.\n");
|
pascal@25624
|
157 int tty_fd = open("/dev/tty", O_NOCTTY | O_WRONLY);
|
pascal@25624
|
158 if(tty_fd < 0)
|
pascal@25624
|
159 {
|
pascal@25624
|
160 print_error("error at open TTY, %s\n", strerror(errno));
|
pascal@25624
|
161 exit(1);
|
pascal@25624
|
162 }
|
pascal@25624
|
163 tty_file = fdopen(tty_fd, "w");
|
pascal@25624
|
164 atexit(exit_video);
|
pascal@25624
|
165 video_initialized = 1;
|
pascal@25624
|
166
|
pascal@25624
|
167 // Fill the functionality table
|
pascal@25624
|
168 memory[0xC0100] = 0x08; // Only mode 3 supported
|
pascal@25624
|
169 memory[0xC0101] = 0x00;
|
pascal@25624
|
170 memory[0xC0102] = 0x00;
|
pascal@25624
|
171 memory[0xC0107] = 0x07; // Support 300, 350 and 400 scanlines
|
pascal@25624
|
172 memory[0xC0108] = 0x00; // Active character blocks?
|
pascal@25624
|
173 memory[0xC0109] = 0x00; // MAximum character blocks?
|
pascal@25624
|
174 memory[0xC0108] = 0xFF; // Support functions
|
pascal@25624
|
175
|
pascal@25624
|
176 // Set video mode
|
pascal@25624
|
177 set_text_mode(1, 80, 25);
|
pascal@25624
|
178 clear_terminal();
|
pascal@25624
|
179 term_needs_update = 0;
|
pascal@25624
|
180 term_cursor = 1;
|
pascal@25624
|
181 term_color = 0x07;
|
pascal@25624
|
182 }
|
pascal@25624
|
183
|
pascal@25624
|
184 int video_active(void)
|
pascal@25624
|
185 {
|
pascal@25624
|
186 return video_initialized;
|
pascal@25624
|
187 }
|
pascal@25624
|
188
|
pascal@25624
|
189 static void set_color(uint8_t c)
|
pascal@25624
|
190 {
|
pascal@25624
|
191 if(term_color != c)
|
pascal@25624
|
192 {
|
pascal@25624
|
193 static char cn[8] = "04261537";
|
pascal@25624
|
194 fprintf(tty_file, (c & 0x80) ? "\x1b[%c;9%c;10%cm" : "\x1b[%c;3%c;4%cm", (c & 0x08) ? '1' : '0', cn[c & 7],
|
pascal@25624
|
195 cn[(c >> 4) & 7]);
|
pascal@25624
|
196 term_color = c;
|
pascal@25624
|
197 }
|
pascal@25624
|
198 }
|
pascal@25624
|
199
|
pascal@25624
|
200 // Writes a DOS character to the current terminal position
|
pascal@25624
|
201 static void put_vc(uint8_t c)
|
pascal@25624
|
202 {
|
pascal@25624
|
203 uint16_t uc = get_unicode(c);
|
pascal@25624
|
204 if(uc < 128)
|
pascal@25624
|
205 putc(uc, tty_file);
|
pascal@25624
|
206 else if(uc < 0x800)
|
pascal@25624
|
207 {
|
pascal@25624
|
208 putc(0xC0 | (uc >> 6), tty_file);
|
pascal@25624
|
209 putc(0x80 | (uc & 0x3F), tty_file);
|
pascal@25624
|
210 }
|
pascal@25624
|
211 else
|
pascal@25624
|
212 {
|
pascal@25624
|
213 putc(0xE0 | (uc >> 12), tty_file);
|
pascal@25624
|
214 putc(0x80 | ((uc >> 6) & 0x3F), tty_file);
|
pascal@25624
|
215 putc(0x80 | (uc & 0x3F), tty_file);
|
pascal@25624
|
216 }
|
pascal@25624
|
217 }
|
pascal@25624
|
218
|
pascal@25624
|
219 // Move terminal cursor to the position
|
pascal@25624
|
220 static void term_goto_xy(unsigned x, unsigned y)
|
pascal@25624
|
221 {
|
pascal@25624
|
222 if(term_posy < y && (int)term_posy < output_row)
|
pascal@25624
|
223 {
|
pascal@25624
|
224 int inc = (int)y < output_row ? y - term_posy : output_row - term_posy;
|
pascal@25624
|
225 fprintf(tty_file, "\x1b[%dB", inc);
|
pascal@25624
|
226 term_posy += inc;
|
pascal@25624
|
227 }
|
pascal@25624
|
228 if(term_posy < y)
|
pascal@25624
|
229 {
|
pascal@25624
|
230 putc('\r', tty_file);
|
pascal@25624
|
231 // Set background color to black, as some terminals insert lines with
|
pascal@25624
|
232 // the current background color.
|
pascal@25624
|
233 set_color(term_color & 0x0F);
|
pascal@25624
|
234 // TODO: Draw new line with background color from video screen
|
pascal@25624
|
235 for(unsigned i = term_posy; i < y; i++)
|
pascal@25624
|
236 putc('\n', tty_file);
|
pascal@25624
|
237 term_posx = 0;
|
pascal@25624
|
238 term_posy = y;
|
pascal@25624
|
239 }
|
pascal@25624
|
240 if(term_posy > y)
|
pascal@25624
|
241 {
|
pascal@25624
|
242 fprintf(tty_file, "\x1b[%dA", term_posy - y);
|
pascal@25624
|
243 term_posy = y;
|
pascal@25624
|
244 }
|
pascal@25624
|
245 if(x == 0 && term_posx != 0)
|
pascal@25624
|
246 {
|
pascal@25624
|
247 putc('\r', tty_file);
|
pascal@25624
|
248 term_posx = 0;
|
pascal@25624
|
249 }
|
pascal@25624
|
250 if(term_posx < x)
|
pascal@25624
|
251 {
|
pascal@25624
|
252 fprintf(tty_file, "\x1b[%dC", x - term_posx);
|
pascal@25624
|
253 term_posx = x;
|
pascal@25624
|
254 }
|
pascal@25624
|
255 if(term_posx > x)
|
pascal@25624
|
256 {
|
pascal@25624
|
257 fprintf(tty_file, "\x1b[%dD", term_posx - x);
|
pascal@25624
|
258 term_posx = x;
|
pascal@25624
|
259 }
|
pascal@25624
|
260 }
|
pascal@25624
|
261
|
pascal@25624
|
262 // Outputs a character with the given attributes at the given position
|
pascal@25624
|
263 static void put_vc_xy(uint8_t vc, uint8_t color, unsigned x, unsigned y)
|
pascal@25624
|
264 {
|
pascal@25624
|
265 term_goto_xy(x, y);
|
pascal@25624
|
266 set_color(color);
|
pascal@25624
|
267
|
pascal@25624
|
268 put_vc(vc);
|
pascal@25624
|
269 term_posx++;
|
pascal@25624
|
270 if(term_posx >= term_sx)
|
pascal@25624
|
271 {
|
pascal@25624
|
272 term_posx = 0;
|
pascal@25624
|
273 term_posy++;
|
pascal@25624
|
274 }
|
pascal@25624
|
275
|
pascal@25624
|
276 if(output_row < (int)term_posy)
|
pascal@25624
|
277 output_row = term_posy;
|
pascal@25624
|
278 }
|
pascal@25624
|
279
|
pascal@25624
|
280 // Compares current screen with memory data
|
pascal@25624
|
281 void check_screen(void)
|
pascal@25624
|
282 {
|
pascal@25624
|
283 // Exit if not in video mode
|
pascal@25624
|
284 if(!video_initialized)
|
pascal@25624
|
285 return;
|
pascal@25624
|
286
|
pascal@25624
|
287 uint16_t memp = (vid_page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
|
pascal@25624
|
288 uint16_t *vm = (uint16_t *)(memory + 0xB8000 + memp);
|
pascal@25624
|
289 unsigned max = output_row + 1;
|
pascal@25624
|
290 for(unsigned y = output_row + 1; y < vid_sy; y++)
|
pascal@25624
|
291 for(unsigned x = 0; x < vid_sx; x++)
|
pascal@25624
|
292 if(vm[x + y * vid_sx] != term_screen[y][x])
|
pascal@25624
|
293 max = y + 1;
|
pascal@25624
|
294
|
pascal@25624
|
295 for(unsigned y = 0; y < max; y++)
|
pascal@25624
|
296 for(unsigned x = 0; x < vid_sx; x++)
|
pascal@25624
|
297 {
|
pascal@25624
|
298 int16_t vc = vm[x + y * vid_sx];
|
pascal@25624
|
299 if(vc != term_screen[y][x])
|
pascal@25624
|
300 {
|
pascal@25624
|
301 // Output character
|
pascal@25624
|
302 term_screen[y][x] = vc;
|
pascal@25624
|
303 put_vc_xy(vc & 0xFF, vc >> 8, x, y);
|
pascal@25624
|
304 }
|
pascal@25624
|
305 }
|
pascal@25624
|
306 if(term_cursor != vid_cursor)
|
pascal@25624
|
307 {
|
pascal@25624
|
308 term_cursor = vid_cursor;
|
pascal@25624
|
309 if(vid_cursor)
|
pascal@25624
|
310 fputs("\x1b[?25h", tty_file);
|
pascal@25624
|
311 else
|
pascal@25624
|
312 fputs("\x1b[?25l", tty_file);
|
pascal@25624
|
313 }
|
pascal@25624
|
314 if(term_cursor)
|
pascal@25624
|
315 // Move cursor
|
pascal@25624
|
316 term_goto_xy(vid_posx[vid_page], vid_posy[vid_page]);
|
pascal@25624
|
317 fflush(tty_file);
|
pascal@25624
|
318 }
|
pascal@25624
|
319
|
pascal@25624
|
320 static void vid_scroll_up(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, int n,
|
pascal@25624
|
321 int page)
|
pascal@25624
|
322 {
|
pascal@25624
|
323 debug(debug_video, "scroll up %d: (%d, %d) - (%d, %d)\n", n, x0, y0, x1, y1);
|
pascal@25624
|
324
|
pascal@25624
|
325 // Check parameters
|
pascal@25624
|
326 if(x1 >= vid_sx)
|
pascal@25624
|
327 x1 = vid_sx - 1;
|
pascal@25624
|
328 if(y1 >= vid_sy)
|
pascal@25624
|
329 y1 = vid_sy - 1;
|
pascal@25624
|
330 if(y0 > y1 || x0 > x1)
|
pascal@25624
|
331 return;
|
pascal@25624
|
332 if(n > y1 - y0 + 1 || !n)
|
pascal@25624
|
333 n = y1 + 1 - y0;
|
pascal@25624
|
334
|
pascal@25624
|
335 // Scroll TERMINAL if we are scrolling (almost) the entire screen
|
pascal@25624
|
336 if(y0 == 0 && y1 >= vid_sy - 2 && x0 < 2 && x1 >= vid_sx - 2)
|
pascal@25624
|
337 {
|
pascal@25624
|
338 // Update screen before
|
pascal@25624
|
339 check_screen();
|
pascal@25624
|
340 int m = n > output_row + 1 ? output_row + 1 : n;
|
pascal@25624
|
341 if(term_posy < m)
|
pascal@25624
|
342 term_goto_xy(0, m);
|
pascal@25624
|
343 output_row -= m;
|
pascal@25624
|
344 term_posy -= m;
|
pascal@25624
|
345 for(unsigned y = 0; y + m < term_sy; y++)
|
pascal@25624
|
346 for(unsigned x = 0; x < term_sx; x++)
|
pascal@25624
|
347 term_screen[y][x] = term_screen[y + m][x];
|
pascal@25624
|
348 for(unsigned y = term_sy - m; y < term_sy; y++)
|
pascal@25624
|
349 for(unsigned x = 0; x < term_sx; x++)
|
pascal@25624
|
350 term_screen[y][x] = 0x0720;
|
pascal@25624
|
351 }
|
pascal@25624
|
352
|
pascal@25624
|
353 // Scroll VIDEO
|
pascal@25624
|
354 uint16_t memp = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
|
pascal@25624
|
355 uint16_t *vm = (uint16_t *)(memory + 0xB8000 + memp);
|
pascal@25624
|
356 for(unsigned y = y0; y + n <= y1; y++)
|
pascal@25624
|
357 for(unsigned x = x0; x <= x1; x++)
|
pascal@25624
|
358 vm[x + y * vid_sx] = vm[x + y * vid_sx + n * vid_sx];
|
pascal@25624
|
359 // Set last rows
|
pascal@25624
|
360 for(unsigned y = y1 - (n - 1); y <= y1; y++)
|
pascal@25624
|
361 for(unsigned x = x0; x <= x1; x++)
|
pascal@25624
|
362 vm[x + y * vid_sx] = (vid_color << 8) + 0x20;
|
pascal@25624
|
363 }
|
pascal@25624
|
364
|
pascal@25624
|
365 static void vid_scroll_dwn(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, unsigned n,
|
pascal@25624
|
366 int page)
|
pascal@25624
|
367 {
|
pascal@25624
|
368 debug(debug_video, "scroll down %d: (%d, %d) - (%d, %d)\n", n, x0, y0, x1, y1);
|
pascal@25624
|
369
|
pascal@25624
|
370 // Check parameters
|
pascal@25624
|
371 if(x1 >= vid_sx)
|
pascal@25624
|
372 x1 = vid_sx - 1;
|
pascal@25624
|
373 if(y1 >= vid_sy)
|
pascal@25624
|
374 y1 = vid_sy - 1;
|
pascal@25624
|
375 if(y0 > y1 || x0 > x1)
|
pascal@25624
|
376 return;
|
pascal@25624
|
377 if(n > y1 - y0 + 1 || !n)
|
pascal@25624
|
378 n = y1 + 1 - y0;
|
pascal@25624
|
379
|
pascal@25624
|
380 // TODO: try to scroll TERMINAL
|
pascal@25624
|
381
|
pascal@25624
|
382 // Scroll VIDEO
|
pascal@25624
|
383 uint16_t memp = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
|
pascal@25624
|
384 uint16_t *vm = (uint16_t *)(memory + 0xB8000 + memp);
|
pascal@25624
|
385 for(unsigned y = y1; y >= y0 + n; y--)
|
pascal@25624
|
386 for(unsigned x = x0; x <= x1; x++)
|
pascal@25624
|
387 vm[x + y * vid_sx] = vm[x + y * vid_sx - n * vid_sx];
|
pascal@25624
|
388 // Set first rows
|
pascal@25624
|
389 for(unsigned y = y0; y < y0 + n; y++)
|
pascal@25624
|
390 for(unsigned x = x0; x <= x1; x++)
|
pascal@25624
|
391 vm[x + y * vid_sx] = (vid_color << 8) + 0x20;
|
pascal@25624
|
392 }
|
pascal@25624
|
393
|
pascal@25624
|
394 static void set_xy(unsigned x, unsigned y, uint16_t c, uint16_t mask, int page)
|
pascal@25624
|
395 {
|
pascal@25624
|
396 uint16_t mem = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
|
pascal@25624
|
397 uint16_t *vm = (uint16_t *)(memory + 0xB8000 + mem);
|
pascal@25624
|
398 vm[x + y * vid_sx] = (vm[x + y * vid_sx] & mask) | c;
|
pascal@25624
|
399 }
|
pascal@25624
|
400
|
pascal@25624
|
401 static uint16_t get_xy(unsigned x, unsigned y, int page)
|
pascal@25624
|
402 {
|
pascal@25624
|
403 uint16_t mem = (page & 7) * (vid_sy > 25 ? 0x2000 : 0x1000);
|
pascal@25624
|
404 uint16_t *vm = (uint16_t *)(memory + 0xB8000 + mem);
|
pascal@25624
|
405 return vm[x + y * vid_sx];
|
pascal@25624
|
406 }
|
pascal@25624
|
407
|
pascal@25624
|
408 static void video_putchar(uint8_t ch, uint16_t at, int page)
|
pascal@25624
|
409 {
|
pascal@25624
|
410 page = page & 7;
|
pascal@25624
|
411 if(ch == 0x0A)
|
pascal@25624
|
412 {
|
pascal@25624
|
413 vid_posy[page]++;
|
pascal@25624
|
414 while(vid_posy[page] >= vid_sy)
|
pascal@25624
|
415 {
|
pascal@25624
|
416 vid_posy[page] = vid_sy - 1;
|
pascal@25624
|
417 vid_scroll_up(0, 0, vid_sx - 1, vid_sy - 1, 1, page);
|
pascal@25624
|
418 }
|
pascal@25624
|
419 }
|
pascal@25624
|
420 else if(ch == 0x0D)
|
pascal@25624
|
421 vid_posx[page] = 0;
|
pascal@25624
|
422 else if(ch == 0x08)
|
pascal@25624
|
423 {
|
pascal@25624
|
424 if(vid_posx[page] > 0)
|
pascal@25624
|
425 vid_posx[page]--;
|
pascal@25624
|
426 }
|
pascal@25624
|
427 else
|
pascal@25624
|
428 {
|
pascal@25624
|
429 if(at & 0xFFFF)
|
pascal@25624
|
430 set_xy(vid_posx[page], vid_posy[page], ch, 0xFF00, page);
|
pascal@25624
|
431 else
|
pascal@25624
|
432 set_xy(vid_posx[page], vid_posy[page], ch + (at << 8), 0, page);
|
pascal@25624
|
433 vid_posx[page]++;
|
pascal@25624
|
434 if(vid_posx[page] >= vid_sx)
|
pascal@25624
|
435 {
|
pascal@25624
|
436 vid_posx[page] = 0;
|
pascal@25624
|
437 vid_posy[page]++;
|
pascal@25624
|
438 while(vid_posy[page] >= vid_sy)
|
pascal@25624
|
439 {
|
pascal@25624
|
440 vid_posy[page] = vid_sy - 1;
|
pascal@25624
|
441 vid_scroll_up(0, 0, vid_sx - 1, vid_sy - 1, 1, page);
|
pascal@25624
|
442 }
|
pascal@25624
|
443 }
|
pascal@25624
|
444 }
|
pascal@25624
|
445 update_posxy();
|
pascal@25624
|
446 }
|
pascal@25624
|
447
|
pascal@25624
|
448 void video_putch(char ch)
|
pascal@25624
|
449 {
|
pascal@25624
|
450 debug(debug_video, "putchar %02x at (%d,%d)\n", ch & 0xFF, vid_posx[vid_page],
|
pascal@25624
|
451 vid_posy[vid_page]);
|
pascal@25624
|
452 video_putchar(ch, 0xFF00, vid_page);
|
pascal@25624
|
453 }
|
pascal@25624
|
454
|
pascal@25624
|
455 // VIDEO int
|
pascal@25624
|
456 void int10()
|
pascal@25624
|
457 {
|
pascal@25624
|
458 debug(debug_int, "V-10%04X: BX=%04X\n", cpuGetAX(), cpuGetBX());
|
pascal@25624
|
459 debug(debug_video, "V-10%04X: BX=%04X\n", cpuGetAX(), cpuGetBX());
|
pascal@25624
|
460 if(!video_initialized)
|
pascal@25624
|
461 init_video();
|
pascal@25624
|
462 unsigned ax = cpuGetAX();
|
pascal@25624
|
463 switch(ax >> 8)
|
pascal@25624
|
464 {
|
pascal@25624
|
465 case 0x00: // SET VIDEO MODE
|
pascal@25624
|
466 if((ax & 0x7F) > 3)
|
pascal@25624
|
467 debug(debug_video, "-> SET GRAPHICS MODE %x<-\n", ax & 0xFF);
|
pascal@25624
|
468 else
|
pascal@25624
|
469 {
|
pascal@25624
|
470 set_text_mode((ax & 0x80) == 0, (ax < 2) ? 40 : 80, 25);
|
pascal@25624
|
471 vid_no_blank = ax & 0x80;
|
pascal@25624
|
472 }
|
pascal@25624
|
473 break;
|
pascal@25624
|
474 case 0x01: // SET CURSOR SHAPE
|
pascal@25624
|
475 if((cpuGetCX() & 0x6000) == 0x2000) // Hide cursor
|
pascal@25624
|
476 vid_cursor = 0;
|
pascal@25624
|
477 else
|
pascal@25624
|
478 vid_cursor = 1;
|
pascal@25624
|
479 break;
|
pascal@25624
|
480 case 0x02: // SET CURSOR POS
|
pascal@25624
|
481 {
|
pascal@25624
|
482 int page = (cpuGetBX() >> 8) & 7;
|
pascal@25624
|
483 vid_posx[page] = cpuGetDX() & 0xFF;
|
pascal@25624
|
484 vid_posy[page] = cpuGetDX() >> 8;
|
pascal@25624
|
485 if(vid_posx[page] >= vid_sx)
|
pascal@25624
|
486 vid_posx[page] = vid_sx - 1;
|
pascal@25624
|
487 if(vid_posy[page] >= vid_sy)
|
pascal@25624
|
488 vid_posy[page] = vid_sy - 1;
|
pascal@25624
|
489 update_posxy();
|
pascal@25624
|
490 break;
|
pascal@25624
|
491 }
|
pascal@25624
|
492 case 0x03: // GET CURSOR POS
|
pascal@25624
|
493 {
|
pascal@25624
|
494 int page = (cpuGetBX() >> 8) & 7;
|
pascal@25624
|
495 cpuSetDX(vid_posx[page] + (vid_posy[page] << 8));
|
pascal@25624
|
496 cpuSetCX(0x0010);
|
pascal@25624
|
497 break;
|
pascal@25624
|
498 }
|
pascal@25624
|
499 case 0x05: // SELECT DISPLAY PAGE
|
pascal@25624
|
500 if((ax & 0xFF) > 7)
|
pascal@25624
|
501 debug(debug_video, "WARN: Select display page > 7!\n");
|
pascal@25624
|
502 else
|
pascal@25624
|
503 {
|
pascal@25624
|
504 vid_page = ax & 7;
|
pascal@25624
|
505 update_posxy();
|
pascal@25624
|
506 }
|
pascal@25624
|
507 break;
|
pascal@25624
|
508 case 0x06: // SCROLL UP WINDOW
|
pascal@25624
|
509 {
|
pascal@25624
|
510 uint16_t cx = cpuGetCX(), dx = cpuGetDX();
|
pascal@25624
|
511 vid_color = cpuGetBX() >> 8;
|
pascal@25624
|
512 vid_scroll_up(cx, cx >> 8, dx, dx >> 8, ax & 0xFF, vid_page);
|
pascal@25624
|
513 break;
|
pascal@25624
|
514 }
|
pascal@25624
|
515 case 0x07: // SCROLL DOWN WINDOW
|
pascal@25624
|
516 {
|
pascal@25624
|
517 uint16_t cx = cpuGetCX(), dx = cpuGetDX();
|
pascal@25624
|
518 vid_color = cpuGetBX() >> 8;
|
pascal@25624
|
519 vid_scroll_dwn(cx, cx >> 8, dx, dx >> 8, ax & 0xFF, vid_page);
|
pascal@25624
|
520 break;
|
pascal@25624
|
521 }
|
pascal@25624
|
522 case 0x08: // READ CHAR AT CURSOR
|
pascal@25624
|
523 {
|
pascal@25624
|
524 int page = (cpuGetBX() >> 8) & 7;
|
pascal@25624
|
525 cpuSetAX(get_xy(vid_posx[page], vid_posy[page], page));
|
pascal@25624
|
526 break;
|
pascal@25624
|
527 }
|
pascal@25624
|
528 case 0x09: // WRITE CHAR AT CURSOR
|
pascal@25624
|
529 case 0x0A: // WRITE CHAR ONLY AT CURSOR
|
pascal@25624
|
530 {
|
pascal@25624
|
531 int page = (cpuGetBX() >> 8) & 7;
|
pascal@25624
|
532 uint16_t px = vid_posx[page];
|
pascal@25624
|
533 uint16_t py = vid_posy[page];
|
pascal@25624
|
534 uint16_t mask = (ax & 0x0100) ? 0 : 0xFF00;
|
pascal@25624
|
535 uint16_t ch = ((ax & 0xFF) | (cpuGetBX() << 8)) & ~mask;
|
pascal@25624
|
536 for(int i = cpuGetCX(); i > 0; i--)
|
pascal@25624
|
537 {
|
pascal@25624
|
538 set_xy(px, py, ch, mask, page);
|
pascal@25624
|
539 px++;
|
pascal@25624
|
540 if(px >= vid_sx)
|
pascal@25624
|
541 {
|
pascal@25624
|
542 px = 0;
|
pascal@25624
|
543 py++;
|
pascal@25624
|
544 if(py >= vid_sy)
|
pascal@25624
|
545 py = 0;
|
pascal@25624
|
546 }
|
pascal@25624
|
547 }
|
pascal@25624
|
548 break;
|
pascal@25624
|
549 }
|
pascal@25624
|
550 case 0x0E: // TELETYPE OUTPUT
|
pascal@25624
|
551 video_putchar(ax, 0xFF00, (cpuGetBX() >> 8) & 7);
|
pascal@25624
|
552 break;
|
pascal@25624
|
553 case 0x0F: // GET CURRENT VIDEO MODE
|
pascal@25624
|
554 cpuSetAX((vid_sx << 8) | 0x0003 | vid_no_blank); // 80x25 mode
|
pascal@25624
|
555 cpuSetBX((vid_page << 8) | (0xFF & cpuGetBX()));
|
pascal@25624
|
556 break;
|
pascal@25624
|
557 case 0x10:
|
pascal@25624
|
558 if(ax == 0x1002) // TODO: Set pallete registers - ignore
|
pascal@25624
|
559 break;
|
pascal@25624
|
560 else if(ax == 0x1003) // TODO: Set blinking state
|
pascal@25624
|
561 break;
|
pascal@25624
|
562 debug(debug_video, "UNHANDLED INT 10, AX=%04x\n", ax);
|
pascal@25624
|
563 break;
|
pascal@25624
|
564 case 0x11:
|
pascal@25624
|
565 if(ax == 0x1130)
|
pascal@25624
|
566 {
|
pascal@25624
|
567 cpuSetDX((vid_sy - 1) & 0xFF);
|
pascal@25624
|
568 cpuSetCX(vid_font_lines);
|
pascal@25624
|
569 }
|
pascal@25624
|
570 else if(ax == 0x1104 || ax == 0x1111 || ax == 0x1114)
|
pascal@25624
|
571 {
|
pascal@25624
|
572 // Clear end-of-screen
|
pascal@25624
|
573 unsigned max = get_last_used_row();
|
pascal@25624
|
574 debug(debug_video, "set 25 lines mode %d\n", max);
|
pascal@25624
|
575 if(max > 25)
|
pascal@25624
|
576 {
|
pascal@25624
|
577 term_goto_xy(0, 24);
|
pascal@25624
|
578 set_color(0x07);
|
pascal@25624
|
579 fputs("\x1b[J", tty_file);
|
pascal@25624
|
580 for(int y = 25; y < 64; y++)
|
pascal@25624
|
581 for(int x = 0; x < 256; x++)
|
pascal@25624
|
582 term_screen[y][x] = 0x0720;
|
pascal@25624
|
583 if(output_row > 24)
|
pascal@25624
|
584 output_row = 24;
|
pascal@25624
|
585 }
|
pascal@25624
|
586 // Set 8x16 font - 80x25 mode:
|
pascal@25624
|
587 vid_sy = 25;
|
pascal@25624
|
588 vid_font_lines = 16;
|
pascal@25624
|
589 memory[0x484] = vid_sy - 1;
|
pascal@25624
|
590 update_posxy();
|
pascal@25624
|
591 }
|
pascal@25624
|
592 else if(ax == 0x1102 || ax == 0x1112)
|
pascal@25624
|
593 {
|
pascal@25624
|
594 // Set 8x8 font - 80x43 or 80x50 mode:
|
pascal@25624
|
595 debug(debug_video, "set 43/50 lines mode\n");
|
pascal@25624
|
596 // Hack - QBASIC.EXE assumes that the mode is always 50 lines on VGA,
|
pascal@25624
|
597 // and *sets* the height into the BIOS area!
|
pascal@25624
|
598 if(memory[0x484] > 42)
|
pascal@25624
|
599 vid_sy = 50;
|
pascal@25624
|
600 else
|
pascal@25624
|
601 vid_sy = 43;
|
pascal@25624
|
602 vid_font_lines = 8;
|
pascal@25624
|
603 memory[0x484] = vid_sy - 1;
|
pascal@25624
|
604 update_posxy();
|
pascal@25624
|
605 }
|
pascal@25624
|
606 break;
|
pascal@25624
|
607 case 0x12: // ALT FUNCTION SELECT
|
pascal@25624
|
608 {
|
pascal@25624
|
609 int bl = cpuGetBX() & 0xFF;
|
pascal@25624
|
610 if(bl == 0x10) // GET EGA INFO
|
pascal@25624
|
611 {
|
pascal@25624
|
612 cpuSetBX(0x0003);
|
pascal@25624
|
613 cpuSetCX(0x0000);
|
pascal@25624
|
614 cpuSetAX(0);
|
pascal@25624
|
615 }
|
pascal@25624
|
616 else if(bl == 0x30) // SET VERTICAL RESOLUTION
|
pascal@25624
|
617 {
|
pascal@25624
|
618 // TODO: select 25/28 lines
|
pascal@25624
|
619 cpuSetAX(0x1212);
|
pascal@25624
|
620 }
|
pascal@25624
|
621 else
|
pascal@25624
|
622 debug(debug_video, "UNHANDLED INT 10, AH=12 BL=%02x\n", bl);
|
pascal@25624
|
623 }
|
pascal@25624
|
624 break;
|
pascal@25624
|
625 case 0x13: // WRITE STRING
|
pascal@25624
|
626 {
|
pascal@25624
|
627 int page = (cpuGetBX() >> 8) & 7;
|
pascal@25624
|
628 vid_posx[page] = cpuGetDX() & 0xFF;
|
pascal@25624
|
629 vid_posy[page] = cpuGetDX() >> 8;
|
pascal@25624
|
630 if(vid_posx[page] >= vid_sx)
|
pascal@25624
|
631 vid_posx[page] = vid_sx - 1;
|
pascal@25624
|
632 if(vid_posy[page] >= vid_sy)
|
pascal@25624
|
633 vid_posy[page] = vid_sy - 1;
|
pascal@25624
|
634 int save_posx = vid_posx[page];
|
pascal@25624
|
635 int save_posy = vid_posy[page];
|
pascal@25624
|
636 int addr = cpuGetAddrES(cpuGetBP());
|
pascal@25624
|
637 int cnt = cpuGetCX();
|
pascal@25624
|
638 if(ax & 2)
|
pascal@25624
|
639 {
|
pascal@25624
|
640 while(cnt && addr < 0xFFFFF)
|
pascal@25624
|
641 {
|
pascal@25624
|
642 video_putchar(memory[addr], memory[addr + 1], page);
|
pascal@25624
|
643 addr += 2;
|
pascal@25624
|
644 cnt--;
|
pascal@25624
|
645 }
|
pascal@25624
|
646 }
|
pascal@25624
|
647 else
|
pascal@25624
|
648 {
|
pascal@25624
|
649 uint8_t at = cpuGetBX() >> 8;
|
pascal@25624
|
650 while(cnt && addr <= 0xFFFFF)
|
pascal@25624
|
651 {
|
pascal@25624
|
652 video_putchar(memory[addr], at, page);
|
pascal@25624
|
653 addr++;
|
pascal@25624
|
654 cnt--;
|
pascal@25624
|
655 }
|
pascal@25624
|
656 }
|
pascal@25624
|
657 if(!(ax & 1))
|
pascal@25624
|
658 {
|
pascal@25624
|
659 vid_posx[page] = save_posx;
|
pascal@25624
|
660 vid_posy[page] = save_posy;
|
pascal@25624
|
661 }
|
pascal@25624
|
662 update_posxy();
|
pascal@25624
|
663 }
|
pascal@25624
|
664 break;
|
pascal@25624
|
665 case 0x1A: // GET/SET DISPLAY COMBINATION CODE
|
pascal@25624
|
666 cpuSetAX(0x001A);
|
pascal@25624
|
667 cpuSetBX(0x0008); // VGA + analog color display
|
pascal@25624
|
668 break;
|
pascal@25624
|
669 case 0x1B: // STATE INFO
|
pascal@25624
|
670 if(cpuGetBX() == 0x0000)
|
pascal@25624
|
671 {
|
pascal@25624
|
672 int addr = cpuGetAddrES(cpuGetDI());
|
pascal@25624
|
673 if(addr < 0xFFF00)
|
pascal@25624
|
674 {
|
pascal@25624
|
675 // Store state information
|
pascal@25624
|
676 memset(memory + addr, 0, 64);
|
pascal@25624
|
677 memory[addr + 0] = 0x00;
|
pascal@25624
|
678 memory[addr + 1] = 0x01;
|
pascal@25624
|
679 memory[addr + 2] = 0x00;
|
pascal@25624
|
680 memory[addr + 3] = 0xC0; // static-func table at C000:0000
|
pascal@25624
|
681 memory[addr + 4] = 0x03; // Video mode
|
pascal@25624
|
682 memory[addr + 5] = vid_sx;
|
pascal@25624
|
683 memory[addr + 6] = vid_sx >> 8;
|
pascal@25624
|
684 for(int i = 0; i < 8; i++)
|
pascal@25624
|
685 {
|
pascal@25624
|
686 memory[addr + 11 + i * 2] = vid_posx[i];
|
pascal@25624
|
687 memory[addr + 12 + i * 2] = vid_posy[i];
|
pascal@25624
|
688 }
|
pascal@25624
|
689 memory[addr + 27] = vid_cursor * 6; // cursor start scanline
|
pascal@25624
|
690 memory[addr + 28] = vid_cursor * 7; // cursor end scanline
|
pascal@25624
|
691 memory[addr + 29] = 0; // current page
|
pascal@25624
|
692 memory[addr + 30] = 0xD4;
|
pascal@25624
|
693 memory[addr + 31] = 0x03; // CRTC port: 03D4
|
pascal@25624
|
694 memory[addr + 34] = vid_sy;
|
pascal@25624
|
695 memory[addr + 35] = vid_font_lines;
|
pascal@25624
|
696 memory[addr + 36] = 0x00; // font lines: 0010
|
pascal@25624
|
697 memory[addr + 39] = 0x10;
|
pascal@25624
|
698 memory[addr + 40] = 0x00; // # of colors: 0010
|
pascal@25624
|
699 memory[addr + 41] = vid_sy > 25 ? 4 : 8; // # of pages
|
pascal@25624
|
700 memory[addr + 42] = 2; // # of scan-lines - get from vid_sy
|
pascal@25624
|
701 memory[addr + 49] = 3; // 256k memory
|
pascal@25624
|
702 cpuSetAX(0x1B1B);
|
pascal@25624
|
703 }
|
pascal@25624
|
704 }
|
pascal@25624
|
705 break;
|
pascal@25624
|
706 case 0xEF: // TEST MSHERC.COM DISPLAY TYPE
|
pascal@25624
|
707 // Ignored
|
pascal@25624
|
708 break;
|
pascal@25624
|
709 default:
|
pascal@25624
|
710 debug(debug_video, "UNHANDLED INT 10, AX=%04x\n", ax);
|
pascal@25624
|
711 }
|
pascal@25624
|
712 }
|
pascal@25624
|
713
|
pascal@25624
|
714 // CRTC port emulation, some software use it to fix "snow" in CGA modes.
|
pascal@25624
|
715 static uint8_t crtc_port;
|
pascal@25624
|
716 static uint16_t crtc_cursor_loc;
|
pascal@25624
|
717
|
pascal@25624
|
718 uint8_t video_crtc_read(int port)
|
pascal@25624
|
719 {
|
pascal@25624
|
720 if(port & 1)
|
pascal@25624
|
721 {
|
pascal@25624
|
722 if(crtc_port == 0x0E)
|
pascal@25624
|
723 return crtc_cursor_loc >> 8;
|
pascal@25624
|
724 if(crtc_port == 0x0F)
|
pascal@25624
|
725 return crtc_cursor_loc;
|
pascal@25624
|
726 else
|
pascal@25624
|
727 return 0;
|
pascal@25624
|
728 }
|
pascal@25624
|
729 else
|
pascal@25624
|
730 return crtc_port;
|
pascal@25624
|
731 }
|
pascal@25624
|
732
|
pascal@25624
|
733 void video_crtc_write(int port, uint8_t value)
|
pascal@25624
|
734 {
|
pascal@25624
|
735 if(port & 1)
|
pascal@25624
|
736 {
|
pascal@25624
|
737 if(crtc_port == 0x0E)
|
pascal@25624
|
738 crtc_cursor_loc = (crtc_cursor_loc & 0xFF) | (value << 8);
|
pascal@25624
|
739 if(crtc_port == 0x0F)
|
pascal@25624
|
740 crtc_cursor_loc = (crtc_cursor_loc & 0xFF00) | (value);
|
pascal@25624
|
741 else
|
pascal@25624
|
742 debug(debug_video, "CRTC port write [%02x] <- %02x\n", crtc_port, value);
|
pascal@25624
|
743 }
|
pascal@25624
|
744 else
|
pascal@25624
|
745 crtc_port = value;
|
pascal@25624
|
746 }
|
pascal@25624
|
747
|
pascal@25624
|
748 int video_get_col(void)
|
pascal@25624
|
749 {
|
pascal@25624
|
750 return vid_posx[vid_page];
|
pascal@25624
|
751 }
|