/* * xep80.c - XEP80 emulation * * Copyright (C) 2007 Mark Grebe * * This file is part of the Atari800 emulator project which emulates * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers. * * Atari800 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 2 of the License, or * (at your option) any later version. * * Atari800 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 Atari800; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #ifdef XEP80_EMULATION #include "xep80.h" #include "xep80_fonts.h" #include "statesav.h" #include "atari.h" #include "antic.h" #include #include "platform.h" #include "util.h" #include "log.h" #if SUPPORTS_CHANGE_VIDEOMODE #include #endif /* SUPPORTS_CHANGE_VIDEOMODE */ #define IN_QUEUE_SIZE 10 /* Definitions for command protocol between the XEP and the Atari */ #define CMD_XX_MASK 0xC0 #define CMD_00 0x00 #define CMD_X_CUR_LOW 0x00 #define CMD_01 0x40 #define CMD_01_MASK 0x70 #define CMD_X_CUR_UPPER 0x40 #define CMD_X_CUR_HIGH 0x50 #define CMD_LEFT_MAR_L 0x60 #define CMD_LEFT_MAR_H 0x70 #define CMD_10 0x80 #define CMD_10_MASK 0xF0 #define CMD_Y_CUR_LOW 0x80 #define CMD_1001 0x90 #define CMD_1001_MASK 0xF8 #define CMD_Y_CUR_HIGH 0x90 #define CMD_Y_CUR_STATUS 0x98 #define CMD_GRAPH_60HZ 0x99 #define CMD_GRAPH_50HZ 0x9A #define CMD_RIGHT_MAR_L 0xA0 #define CMD_RIGHT_MAR_H 0xB0 #define CMD_11 0xC0 #define CMD_GET_CHAR 0xC0 #define CMD_REQ_X_CUR 0xC1 #define CMD_MRST 0xC2 #define CMD_PRT_STAT 0xC3 #define CMD_FILL_PREV 0xC4 #define CMD_FILL_SPACE 0xC5 #define CMD_FILL_EOL 0xC6 #define CMD_CLR_LIST 0xD0 #define CMD_SET_LIST 0xD1 #define CMD_SCR_NORMAL 0xD2 #define CMD_SCR_BURST 0xD3 #define CMD_CHAR_SET_A 0xD4 #define CMD_CHAR_SET_B 0xD5 #define CMD_CHAR_SET_INT 0xD6 #define CMD_TEXT_50HZ 0xD7 #define CMD_CUR_OFF 0xD8 #define CMD_CUR_ON 0xD9 #define CMD_CUR_BLINK 0xDA #define CMD_CUR_ST_LINE 0xDB #define CMD_SET_SCRL_WIN 0xDC #define CMD_SET_PRINT 0xDD #define CMD_WHT_ON_BLK 0xDE #define CMD_BLK_ON_WHT 0xDF #define CMD_VIDEO_CTRL 0xED #define CMD_ATTRIB_A 0xF4 #define CMD_ATTRIB_B 0xF5 #define CHAR_SET_A 0 #define CHAR_SET_B 1 #define CHAR_SET_INTERNAL 2 /* These center the graphics screen inside of the XEP80 screen */ #define GRAPH_X_OFFSET ((XEP80_SCRN_WIDTH - XEP80_GRAPH_WIDTH) / 2) #define GRAPH_Y_OFFSET ((XEP80_scrn_height - XEP80_GRAPH_HEIGHT) / 2) /* Used to determine if a character is double width */ #define IS_DOUBLE(x,y) (((char_data(y, x) & 0x80) && font_b_double) || \ (((char_data(y, x) & 0x80) == 0) && font_a_double)) /* Global variables */ int XEP80_enabled = FALSE; int XEP80_port = 0; int XEP80_char_height = XEP80_CHAR_HEIGHT_NTSC; int XEP80_scrn_height = XEP80_HEIGHT * XEP80_CHAR_HEIGHT_NTSC; /* Local state variables */ static int output_word = 0; static UWORD input_queue[IN_QUEUE_SIZE]; static int input_count = 0; /* Indicates moment when receiving of a word started, or a moment when transmitting of the first word in the output queue started. Used to compare with ANTIC_CPU_CLOCK when determining a bit currently transmitted. */ static unsigned int start_trans_cpu_clock; /* Indicates that a byte is currently being received. */ static int receiving = FALSE; /* Values in internal RAM */ static int ypos = 0; /* location: $01 (R1) */ static int xpos = 0; /* location: $02 (R2) */ static UBYTE last_char = 0; /* location: $04 (R4) */ static int lmargin = 0; /* location: $05 (R5) */ static int rmargin = 0x4f; /* location: $06 (R6) */ static int xscroll = 0; /* location: $1f (RAM bank 1 R7) */ /* 25 pointers to start of data for each line. Originally at locations $20..$38. */ static UBYTE *line_pointers[XEP80_HEIGHT]; static int old_ypos = 0; /* location: $39 */ static int old_xpos = 0; /* location: $3a */ static int list_mode = FALSE; /* location: $3b */ static int escape_mode = FALSE; /* location: $3c */ /* location: $3f */ static int burst_mode = FALSE; /* bit 0 */ static int screen_output = TRUE; /* bit 7; indicates screen/printer */ /* Attribute Latch 0 */ static UBYTE attrib_a = 0xff; static int font_a_index = 0; static int font_a_double = FALSE; static int font_a_blank = FALSE; static int font_a_blink = FALSE; /* Attribute Latch 1 */ static UBYTE attrib_b = 0xff; static int font_b_index = 0; static int font_b_double = FALSE; static int font_b_blank = FALSE; static int font_b_blink = FALSE; /* TCP */ static int cursor_on = TRUE; /* byte 13 */ static int graphics_mode = FALSE; static int pal_mode = FALSE; /* VCR*/ static int blink_reverse = FALSE; /* bit 0 */ static int cursor_blink = FALSE; /* bit 1 */ static int cursor_overwrite = FALSE; /* bit 2 */ static int inverse_mode = FALSE; /* bit 3 */ static int char_set = CHAR_SET_A; /* bits 6-7 */ /* CURS */ static int cursor_x = 0; static int cursor_y = 0; static int curs = 0; /* Address of cursor in video RAM, $0000..$1fff */ static UBYTE video_ram[0x2000]; /* 8 KB of RAM */ #define char_data(y, x) (*(line_pointers[(y)]+(x))) #define graph_data(y, x) (video_ram[(y)*XEP80_GRAPH_WIDTH/8+(x)]) #define tab_stops(x) (video_ram[0x1900+(x)]) static UBYTE const input_mask[2] = {0x02,0x20}; static UBYTE const output_mask[2] = {0x01,0x10}; UBYTE XEP80_screen_1[XEP80_SCRN_WIDTH*XEP80_MAX_SCRN_HEIGHT]; UBYTE XEP80_screen_2[XEP80_SCRN_WIDTH*XEP80_MAX_SCRN_HEIGHT]; UBYTE (*font)[XEP80_FONTS_CHAR_COUNT][XEP80_MAX_CHAR_HEIGHT][XEP80_CHAR_WIDTH]; /* Path to the XEP80's charset ROM image, marked as U12 on Jerzy Sobola's schematic: http://www.dereatari.republika.pl/schematy.htm The ROM image is also available there. */ static char charset_filename[FILENAME_MAX]; static void UpdateTVSystem(void) { XEP80_char_height = pal_mode ? XEP80_CHAR_HEIGHT_PAL : XEP80_CHAR_HEIGHT_NTSC; XEP80_scrn_height = XEP80_HEIGHT * XEP80_char_height; #if SUPPORTS_CHANGE_VIDEOMODE VIDEOMODE_UpdateXEP80(); #endif } /* -------------------------------------- Functions for blitting display buffer. -------------------------------------- */ static void BlitChar(int x, int y, int cur) { int screen_col; int font_row, font_col; UBYTE *from, *to; UBYTE ch; UBYTE on, off, blink; int font_index, font_double, font_blank, font_blink; int blink_rev; int last_double_cur = FALSE; /* Don't Blit characters that aren't on the screen at the moment. */ if (x < xscroll || x >= xscroll + XEP80_LINE_LEN) return; screen_col = x-xscroll; ch = char_data(y, x); /* Dispaly Atari EOL's as spaces */ if (ch == XEP80_ATARI_EOL && ((font_a_index & XEP80_FONTS_BLK_FONT_BIT) == 0) && char_set != CHAR_SET_INTERNAL) ch = 0x20; if (ch & 0x80) { font_index = font_b_index; font_double = font_b_double; font_blank = font_b_blank; font_blink = font_b_blink; } else { font_index = font_a_index; font_double = font_a_double; font_blank = font_a_blank; font_blink = font_a_blink; } if (font_blink && blink_reverse && (font_index & XEP80_FONTS_REV_FONT_BIT)) blink_rev = TRUE; else blink_rev = FALSE; if (inverse_mode) font_index ^= XEP80_FONTS_REV_FONT_BIT; if (ch==XEP80_ATARI_EOL) { if (inverse_mode) font_index |= XEP80_FONTS_REV_FONT_BIT; else font_index &= ~XEP80_FONTS_REV_FONT_BIT; } /* Skip the charcter if the last one was a displayed double */ if (screen_col != 0 && !cur) { if (IS_DOUBLE(x-1,y)) { int firstd; firstd = x-1; while (firstd > xscroll) { if (!IS_DOUBLE(firstd,y)) { firstd++; break; } firstd--; } if ((x-firstd) % 2) return; } } /* Check if we are doing a cursor, and the charcter before is double */ if (cur) { if (screen_col != 0) { if (IS_DOUBLE(x-1,y)) last_double_cur = TRUE; } } if (inverse_mode) { on = XEP80_FONTS_offcolor; off = XEP80_FONTS_oncolor; } else { on = XEP80_FONTS_oncolor; off = XEP80_FONTS_offcolor; } if (font_index & XEP80_FONTS_REV_FONT_BIT) blink = on; else blink = off; if (font_blank) { UBYTE color; to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; for (font_row=0;font_row < XEP80_char_height; font_row++) { if (cur || (font_index & XEP80_FONTS_REV_FONT_BIT)) color = on; else color = off; for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) { if (font_double) *to++ = color; *to++ = color; } if (font_double) to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH; else to += XEP80_SCRN_WIDTH - 1*XEP80_CHAR_WIDTH; } to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; for (font_row=0;font_row < XEP80_char_height; font_row++) { if ((cur && !cursor_blink) || (font_index & XEP80_FONTS_REV_FONT_BIT)) color = on; else color = off; for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) { if (font_double) *to++ = color; *to++ = color; } if (font_double) to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH; else to += XEP80_SCRN_WIDTH - 1*XEP80_CHAR_WIDTH; } } else if (font_double && !cur) { int width; if (screen_col == 79) width = XEP80_CHAR_WIDTH/2; else width = XEP80_CHAR_WIDTH; to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; for (font_row=0;font_row < XEP80_char_height; font_row++) { from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row]; for (font_col=0; font_col < width; font_col++) { *to++ = *from; *to++ = *from++; } to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH; } to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; for (font_row=0;font_row < XEP80_char_height; font_row++) { if (blink_rev) from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row]; else from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row]; for (font_col=0; font_col < width; font_col++) { if (font_blink && !cur && !blink_rev) { if ((font_index & XEP80_FONTS_UNDER_FONT_BIT) && font_row == XEP80_FONTS_UNDER_ROW) { *to++ = *from; *to++ = *from++; } else { *to++ = blink; *to++ = blink; from++; } } else { *to++ = *from; *to++ = *from++; } } to += XEP80_SCRN_WIDTH - 2*XEP80_CHAR_WIDTH; } } else if ((font_double || last_double_cur) && cur && !cursor_overwrite) { int first_half, start_col, end_col; /* Determine if this is a double first or second half */ if (screen_col == 0) first_half = TRUE; else { if (IS_DOUBLE(x-1,y)) { int firstd; firstd = x-1; while (firstd > xscroll) { if (!IS_DOUBLE(firstd,y)) { firstd++; break; } firstd--; } first_half = (((x-firstd) % 2) == 0); } else first_half = TRUE; } if (first_half) { start_col = 0; end_col = 3; } else { start_col = 3; end_col = 6; ch = char_data(y, x-1); } to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; for (font_row=0;font_row < XEP80_char_height; font_row++) { from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row] + start_col; if (first_half) *to++ = *from++; for (font_col=start_col; font_col < end_col; font_col++) { *to++ = *from; *to++ = *from++; } if (!first_half) *to++ = *from; to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; for (font_row=0;font_row < XEP80_char_height; font_row++) { if (!cursor_blink) from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row] + start_col; else from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row] + start_col; if (first_half) *to++ = *from++; for (font_col=start_col; font_col < end_col; font_col++) { *to++ = *from; *to++ = *from++; } if (!first_half) *to++ = *from; to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } } else { to = &XEP80_screen_1[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; if (cur & cursor_overwrite) { for (font_row=0;font_row < XEP80_char_height; font_row++) { for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) *to++ = on; to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } } else { for (font_row=0;font_row < XEP80_char_height; font_row++) { if (cur) from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row]; else from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row]; for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) *to++ = *from++; to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } } to = &XEP80_screen_2[XEP80_SCRN_WIDTH * XEP80_char_height * y + screen_col * XEP80_CHAR_WIDTH]; if (cur & cursor_overwrite) { if (cursor_blink) { for (font_row=0;font_row < XEP80_char_height; font_row++) { for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) *to++ = off; to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } } else { for (font_row=0;font_row < XEP80_char_height; font_row++) { for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) *to++ = on; to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } } } else { for (font_row=0;font_row < XEP80_char_height; font_row++) { if (cur && !cursor_blink) from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row]; else { if (blink_rev) from = XEP80_FONTS_atari_fonts[char_set][font_index ^ XEP80_FONTS_REV_FONT_BIT][ch][font_row]; else from = XEP80_FONTS_atari_fonts[char_set][font_index][ch][font_row]; } for (font_col=0; font_col < XEP80_CHAR_WIDTH; font_col++) { if (font_blink && !cur) { if ((font_index & XEP80_FONTS_UNDER_FONT_BIT) && font_row == XEP80_FONTS_UNDER_ROW) *to++ = *from++; else { *to++ = blink; from++; } } else *to++ = *from++; } to += XEP80_SCRN_WIDTH - XEP80_CHAR_WIDTH; } } } } static void UpdateCursor(void) { if (!graphics_mode && cursor_on) { /* Redraw character cursor was at */ BlitChar(cursor_x, cursor_y, FALSE); /* Handle reblitting double wide's which cursor may have overwritten */ if (cursor_x != 0) BlitChar(cursor_x-1, cursor_y, FALSE); /* Redraw cursor at new location */ BlitChar(xpos, ypos, TRUE); } cursor_x = xpos; cursor_y = ypos; curs = line_pointers[ypos] + xpos - video_ram; } static void BlitCharScreen(void) { int screen_row, screen_col; for (screen_row = 0; screen_row < XEP80_HEIGHT; screen_row++) { for (screen_col = xscroll; screen_col < xscroll + XEP80_LINE_LEN; screen_col++) BlitChar(screen_col, screen_row, FALSE); } UpdateCursor(); } static void BlitRows(int y_start, int y_end) { int screen_row, screen_col; for (screen_row = y_start; screen_row <= y_end; screen_row++) { for (screen_col = xscroll; screen_col < xscroll + XEP80_LINE_LEN; screen_col++) BlitChar(screen_col, screen_row, FALSE); } } static void BlitGraphChar(int x, int y) { int graph_col; UBYTE *to1,*to2; UBYTE ch; UBYTE on, off; if (inverse_mode) { on = XEP80_FONTS_offcolor; off = XEP80_FONTS_oncolor; } else { on = XEP80_FONTS_oncolor; off = XEP80_FONTS_offcolor; } ch = graph_data(y, x); to1 = &XEP80_screen_1[XEP80_SCRN_WIDTH * (y + GRAPH_Y_OFFSET) + x * 8 + GRAPH_X_OFFSET]; to2 = &XEP80_screen_2[XEP80_SCRN_WIDTH * (y + GRAPH_Y_OFFSET) + x * 8 + GRAPH_X_OFFSET]; for (graph_col=0; graph_col < 8; graph_col++) { if (ch & (1< %03x [i]", word, input_count);*/ input_count++; } /* Checks if cursor position changed and sends it back to host. ROM location: 0200 */ static void SendCursorStatus(void) { if (xpos != old_xpos || ypos == old_ypos) { /* Send X cursor position if it changed, or if both X and Y dind't change. */ int pos = xpos > 0x4f ? 0x150 : (xpos | 0x100); if (ypos != old_ypos) /* Indicate Y position will follow */ pos |= 0x80; InputWord(pos); old_xpos = xpos; } if (ypos != old_ypos) { /* Send Y position if it changed. */ InputWord(ypos | 0x1e0); old_ypos = ypos; } } /* Scrolls the screen down starting at line Y. ROM location: 053d */ static void ScrollDown(int y) { UBYTE *ptr = line_pointers[XEP80_HEIGHT-2]; memmove(line_pointers+y+1, line_pointers+y, sizeof(UBYTE*) * (XEP80_HEIGHT-2-y)); line_pointers[y] = ptr; } /* Fills line Y with EOL. */ static void ClearLine(int y) { memset(line_pointers[y]+xscroll, XEP80_ATARI_EOL, XEP80_LINE_LEN); } /* Clears (fills with EOL) the current line and moves cursor to left margin. ROM location: 06b6 */ static void ClearLineCursorToLeftMargin(void) { ClearLine(ypos); xpos = lmargin; } /* Scrolls the whole screen up, and clears the last line. */ static void ScrollScreenUp(void) { UBYTE *ptr = line_pointers[0]; memmove(line_pointers, line_pointers+1, sizeof(UBYTE*) * (XEP80_HEIGHT-2)); line_pointers[XEP80_HEIGHT-2] = ptr; ClearLine(XEP80_HEIGHT-2); } /* Sreolls the whole screen up, clears the last line, and moves cursor to left margin of the last line. ROM location: 0652 */ static void ScrollScreenUpCursorToLeftMargin(void) { ScrollScreenUp(); ypos = XEP80_HEIGHT-2; xpos = lmargin; BlitScreen(); } /* Process the "Insert Line" ATASCII character. ROM location: 0537 */ static void InsertLine(void) { ScrollDown(ypos); ClearLineCursorToLeftMargin(); BlitRows(ypos, XEP80_HEIGHT-2); UpdateCursor(); } /* Advance the cursor right. If necessary, scroll the screen or extend logical line. ROM location: 05cb */ static void AdvanceCursor(UBYTE prev_char_under_cursor) { if (xpos != rmargin) { ++xpos; UpdateCursor(); return; } if (ypos == 23) { /* last non-status line */ ScrollScreenUpCursorToLeftMargin(); UpdateCursor(); return; } if (ypos == 24) { xpos = 0; UpdateCursor(); return; } ++ypos; if (prev_char_under_cursor == XEP80_ATARI_EOL) { InsertLine(); return; } xpos = 0; UpdateCursor(); } /* Add ATASCII character BYTE at cursor position, and advance the cursor. ROM location: 05c3 */ static void AddCharAtCursor(UBYTE byte) { UBYTE prev_char = video_ram[curs]; video_ram[curs] = byte; BlitChar(xpos, ypos, FALSE); escape_mode = FALSE; AdvanceCursor(prev_char); } /* Process the "Cursor Up" ATASCII character. ROM location: 0523 */ static void CursorUp(void) { if (--ypos < 0) ypos = XEP80_HEIGHT-2; UpdateCursor(); } /* Process the "Cursor Down" ATASCII character. ROM location: 052d */ static void CursorDown(void) { if (++ypos > XEP80_HEIGHT-2) ypos = 0; UpdateCursor(); } /* Process the "Cursor Left" ATASCII character. ROM location: 0552 */ static void CursorLeft(void) { if (xpos == lmargin) xpos = rmargin; else --xpos; UpdateCursor(); } /* Process the "Cursor Right" ATASCII character. ROM location: 055c */ static void CursorRight(void) { if (xpos == rmargin) xpos = lmargin; else { if (video_ram[curs] == XEP80_ATARI_EOL) video_ram[curs] = 0x20; ++xpos; } UpdateCursor(); } /* Process the "Clear Screen" ATASCII character. ROM location: 056f */ static void ClearScreen(void) { int y; for (y=0; y ypos; --y) { if (lmargin == rmargin) { video_ram[curs] = prev_dropped; BlitRows(ypos, y_end); UpdateCursor(); return; } else { prev_dropped = ShiftLeft(y, lmargin, prev_dropped); } } if (xpos == rmargin) { video_ram[curs] = prev_dropped; } else { ShiftLeft(y, xpos, prev_dropped); } BlitRows(ypos, y_end); UpdateCursor(); } /* Process the "Insert Character" ATASCII character. ROM location: 047a */ static void InsertChar(void) { UBYTE prev_dropped = 0x20; int y = ypos; int x = xpos; UBYTE to_drop; UBYTE new_last_char; for (;;) { if (y == rmargin) { to_drop = video_ram[curs]; video_ram[curs] = prev_dropped; new_last_char = prev_dropped; } else { to_drop = *(line_pointers[y]+rmargin); memmove(line_pointers[y]+x+1, line_pointers[y]+x, rmargin-x); *(line_pointers[y]+x) = prev_dropped; new_last_char = *(line_pointers[y]+rmargin); } prev_dropped = to_drop; if (to_drop == XEP80_ATARI_EOL) { if (new_last_char == XEP80_ATARI_EOL) break; /* Need to extend logical line. */ if (y == XEP80_HEIGHT - 2) { /* Finished in the last line */ if (ypos == 0) break; ScrollScreenUp(); --ypos; BlitCharScreen(); UpdateCursor(); return; } else { ScrollDown(y+1); ClearLine(y+1); BlitRows(ypos, XEP80_HEIGHT-2); UpdateCursor(); return; } } if (y == XEP80_HEIGHT-2) break; ++y; x = lmargin; } BlitRows(ypos, y); UpdateCursor(); } /* Process the "Tab" ATASCII character. ROM location: 05a7 */ static void GoToNextTab(void) { for (;;) { UBYTE prev = video_ram[curs]; if (prev == XEP80_ATARI_EOL) video_ram[curs] = 0x20; AdvanceCursor(prev); if (xpos == rmargin) return; if (tab_stops(xpos)) return; } } /* Process the "EOL" ATASCII character. ROM location: 0253 */ static void AddEOL(void) { xpos = rmargin; escape_mode = FALSE; AdvanceCursor(0); } /* Process the "Delete Line" ATASCII character. ROM location: 07b4 */ static void DeleteLogicalLine(void) { if (ypos == XEP80_HEIGHT - 2) { ClearLineCursorToLeftMargin(); } else { for (;;) { UBYTE prev = char_data(ypos, rmargin); UBYTE *ptr = line_pointers[ypos]; memmove(line_pointers+ypos, line_pointers+ypos+1, sizeof(UBYTE*) * (XEP80_HEIGHT-2-ypos)); line_pointers[XEP80_HEIGHT - 2] = ptr; /* Clear last line */ memset(ptr+xscroll, XEP80_ATARI_EOL, XEP80_LINE_LEN); if (prev == XEP80_ATARI_EOL) break; } xpos = lmargin; } BlitRows(ypos, XEP80_HEIGHT-2); UpdateCursor(); } /* Reverses bit order of a byte. Bytes have to be reversed in graphics mode, because NS405 displays graphics from LSB (left) to MSB (right). ROM location: 02d7 */ static UBYTE ReverseByte(unsigned long int b) { return ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; } /* Puts a byte of grsphics in RAM (reversing its bit order) and displays it. ROM location: 0296 */ static void AddGraphCharAtCursor(UBYTE byte) { int y = (curs & 0x1fff) / (XEP80_GRAPH_WIDTH/8); video_ram[curs & 0x1fff] = ReverseByte(byte); if (y < XEP80_GRAPH_HEIGHT) { BlitGraphChar((curs & 0x1fff) % (XEP80_GRAPH_WIDTH/8), y); } curs = (curs+1) & 0xffff; } /* Process receiving of a character (ie. not a command). ROM location: around 024b */ static void ReceiveChar(UBYTE byte) { if (!screen_output) { /* Printer characters are thrown away, handled elsewhere. The * XEP80 driver needs to be set up to send printer characters * to the existing P: driver. */ } else if (graphics_mode) AddGraphCharAtCursor(byte); else if (byte == XEP80_ATARI_EOL) AddEOL(); else if (escape_mode || list_mode) AddCharAtCursor(byte); else if (byte == 0x1b) /* Escape - Print next char even if a control char */ escape_mode = TRUE; else if (ypos == 24) { if (byte == 0x9c) { /* Delete Line */ /* ROM location: 07df */ ClearLineCursorToLeftMargin(); BlitRows(ypos, XEP80_HEIGHT-2); UpdateCursor(); } else AddCharAtCursor(byte); } else { switch(byte) { case 0x1c: /* Cursor Up */ CursorUp(); break; case 0x1d: /* Cursor Down */ CursorDown(); break; case 0x1e: /* Cursor Left */ CursorLeft(); break; case 0x1f: /* Cursor Right */ CursorRight(); break; case 0x7d: /* Clear Screen */ ClearScreen(); break; case 0x7e: /* Backspace */ Backspace(); break; case 0x7f: /* Tab */ GoToNextTab(); break; case 0x9c: /* Delete Line */ DeleteLogicalLine(); break; case 0x9d: /* Insert Line */ InsertLine(); break; case 0x9e: /* Clear tab */ tab_stops(xpos) = FALSE; break; case 0x9f: /* Set Tab */ tab_stops(xpos) = TRUE; break; case 0xfd: /* Sound Bell */ /* Do nothing here */ break; case 0xfe: /* Delete Char */ DeleteChar(); break; case 0xff: /* Insert Char */ InsertChar(); break; default: AddCharAtCursor(byte); break; } } } /* Process commands: 100****** - Horizontal Cursor Position ($00-3F) 10100**** - Horizontal Cursor Position ($40-4F) ROM location: 0311 */ static void SetXCur(UBYTE new_xpos) { xpos = old_xpos = new_xpos; UpdateCursor(); } /* Process command 10101**** - Horiz Curs Pos High Nibble - for wide screen ROM location: 0318 */ static void SetXCurHigh(UBYTE new_xcur) { xpos = old_xpos = ((UBYTE)xpos & 0x0f) | ((new_xcur & 0x0f) << 4); UpdateCursor(); } /* Process command 10110**** - Left Margin Low Nibble - sets high nibble to 00 ROM location: 0320 */ static void SetLeftMarginLow(UBYTE margin) { lmargin = margin & 0x0f; } /* Process command 10111**** - Left Margin High Nibble ROM location: 0325 */ static void SetLeftMarginHigh(UBYTE margin) { lmargin = ((UBYTE)lmargin & 0x0f) | ((margin & 0x0f) << 4); } /* Process commands: 11000**** - Vertical Cursor Position ($00-0F) 110010*** - Vertical Cursor Position ($10-17) 110011000 - Set Cursor to Status Row ($18) See caution, Pg. 6 ROM location: 032d, 03fb */ static void SetYCur(UBYTE new_ypos) { ypos = old_ypos = new_ypos; UpdateCursor(); } /* Process command 110011001 - Set Graphics to 60 Hz ROM location: 02b1 */ static void SetGraphics60Hz(void) { graphics_mode = TRUE; blink_reverse = FALSE; cursor_blink = FALSE; cursor_overwrite = FALSE; inverse_mode = FALSE; cursor_x = 0; cursor_y = 0; curs = 0; pal_mode = FALSE; UpdateTVSystem(); BlitGraphScreen(); } /* Process command 110011010 - Modify Graphics to 50 Hz ROM location: 02cd */ static void SetGraphics50Hz(void) { pal_mode = TRUE; UpdateTVSystem(); BlitGraphScreen(); } /* Process command 11010**** - Right Margin Low Nibble - sets high nibble to 04 ROM location: 033a */ static void SetRightMarginLow(UBYTE margin) { rmargin = margin ^ 0xe0; } /* Process command 11011**** - Right Margin High Nibble ROM location: 033f */ static void SetRightMarginHigh(UBYTE margin) { rmargin = ((UBYTE)rmargin & 0x0f) | ((margin & 0x0f) << 4); } /* Process command 111000000 - Get Character from XEP80 at cursor (and advance) ROM location: 034c */ static void GetChar(void) { InputWord(video_ram[curs]); AdvanceCursor(0x00); SendCursorStatus(); } /* Process command 111000001 - Request Horizontal Cursor ROM location: 0613 */ static void GetXCur(void) { InputWord(xpos); } /* Process command 111000010 - Master Reset ROM location: 0615 */ static void MasterReset(void) { ColdStart(); InputWord(0x01); } /* Process command 111000011 - Printer Port Status ROM location: 0631 */ static void GetPrinterStatus(void) { /* Printer port is currently not emulated. */ InputWord(0x01); } /* Process commands 111010000 - Clear List Flag 111010001 - Set List Flag ROM location: 0376 */ static void SetList(int list) { list_mode = list; } /* Process commands 111010010 - Set Screen Normal Mode - cursor returned each char 111010011 - Set Screen Burst Mode - no cursor returned 111011101 - Set Printer Output ROM location: 03e1, 03e7 */ static void SetOutputDevice(int screen, int burst) { screen_output = screen; burst_mode = burst; } /* Process commands 111010100 - Select Character Set A - Atari graphics (ATASCII) 111010101 - Select Character Set B - Atari international 111010110 - Select XEP80 Internal Character Set ROM location: 037c, 039b */ static void SetCharSet(int set) { char_set = set; } /* Process command 111010111 - Modify Text to 50 Hz Operation ROM location: 03a6 */ static void SetText50Hz(void) { pal_mode = TRUE; UpdateTVSystem(); BlitCharScreen(); } /* Process commands 111011000 - Cursor Off 111011001 - Cursor On Continuous 111011010 - Cursor On Blink ROM location: 03af, 03b5 */ static void SetCursorMode(int on, int blink) { cursor_on = on; cursor_blink = blink; if (!graphics_mode) { if (!cursor_on) BlitChar(xpos, ypos, FALSE); else UpdateCursor(); } } /* Process command 111011011 - Move Cursor to Start of Logical Line ROM location: 03c5 */ static void SetXCurStart(void) { for (;;) { if (ypos == 0) break; --ypos; if (char_data(ypos, rmargin) == XEP80_ATARI_EOL) { ++ypos; break; } } UpdateCursor(); } /* Process command 111011100 - Set Scroll Window to Cursor X Value ROM location: 03dc */ static void SetScrollWindow(void) { xscroll = xpos; BlitScreen(); } /* Process commands 111011110 - Select White Characters on Black Background 111011111 - Select Black Characters on White Background ROM location: 03ed */ static void SetInverse(int inverse) { inverse_mode = inverse; BlitScreen(); } /* Process command 111101101 - Reserved ROM location: 0432 */ static void SetVideoCtrl(UBYTE video_ctrl) { if (video_ctrl & 0x08) inverse_mode = TRUE; else inverse_mode = FALSE; if (video_ctrl & 0x02) cursor_blink = FALSE; else cursor_blink = TRUE; if (video_ctrl & 0x04) cursor_overwrite = FALSE; else cursor_overwrite = TRUE; if (video_ctrl & 0x01) blink_reverse = TRUE; else blink_reverse = FALSE; BlitScreen(); } static void UpdateAttributeBits(UBYTE attrib, int *font_index, int *font_double, int *font_blank, int *font_blink) { *font_index = 0; if (!(attrib & 0x01)) *font_index |= XEP80_FONTS_REV_FONT_BIT; if (!(attrib & 0x20)) *font_index |= XEP80_FONTS_UNDER_FONT_BIT; if (!(attrib & 0x80)) *font_index |= XEP80_FONTS_BLK_FONT_BIT; if (!(attrib & 0x10)) *font_double = TRUE; else *font_double = FALSE; if (!(attrib & 0x40)) *font_blank = TRUE; else *font_blank = FALSE; if (!(attrib & 0x04)) *font_blink = TRUE; else *font_blink = FALSE; } /* Process command 111110100 - Reserved ROM location: 044d */ static void SetAttributeA(UBYTE attrib) { attrib_a = attrib; UpdateAttributeBits(attrib, &font_a_index, &font_a_double, &font_a_blank, &font_a_blink); BlitScreen(); } /* Process command 111110101 - Reserved ROM location: 0450 */ static void SetAttributeB(UBYTE attrib) { attrib_b = attrib; UpdateAttributeBits(attrib, &font_b_index, &font_b_double, &font_b_blank, &font_b_blink); BlitScreen(); } /* Process 1111xxxxx "Reserved" commands. In reality they set values of various internal NS405 registers. ROM location: 03ff */ static void SetReserved(UBYTE byte) { byte &= 0x1f; if (byte == 0) return; switch (byte) { case CMD_VIDEO_CTRL & 0x1f: SetVideoCtrl(last_char); break; case CMD_ATTRIB_A & 0x1f: SetAttributeA(last_char); break; case CMD_ATTRIB_B & 0x1f: SetAttributeB(last_char); break; default: /* Other 1111xxxxx reserved commands are not currently emulated - implementation would require exact emulation of the whole NS405. 111100001, 111100010: Set CURS 111100011: Put character under CURS 111100100, 111100101: Put byte into internal RAM 111100110, 111100111: Set HOME 111101000: Set MASK 111101001: Set PSW 111101010: Set PORT 111101011: Set TIMER 111101100: Set SCR 111101110, 111101111: Set BEGD 111110000, 111110001: Set ENDD 111110010, 111110011: Set SROW 111110110: Set TCP 111110111: Put byte under TCP 111111000: Set VINT 111111001, 111111010: Set PSR/BAUD 111111011: Set UCR 111111100: Set UMX 111111101: Set XMTR 111111110: Ignore 111111111: Strobe the parallel port */ Log_print("XEP80 received not emulated command %03h", 0x100 & byte); } last_char = byte - 0x1f; } /* Process a word received from host. */ static void OutputWord(int word) { UBYTE byte = word & 0xFF; /* Is it a command or data word? */ if (word & 0x100) { switch(byte & CMD_XX_MASK) { case CMD_00: SetXCur(byte); break; case CMD_01: switch(byte & CMD_01_MASK) { case CMD_X_CUR_UPPER: SetXCur(byte); break; case CMD_X_CUR_HIGH: SetXCurHigh(byte); break; case CMD_LEFT_MAR_L: SetLeftMarginLow(byte); break; case CMD_LEFT_MAR_H: SetLeftMarginHigh(byte); break; } break; case CMD_10: switch(byte & CMD_10_MASK) { case CMD_Y_CUR_LOW: SetYCur(byte & 0x0F); break; case CMD_1001: if ((byte & CMD_1001_MASK) == CMD_Y_CUR_HIGH) SetYCur(byte & 0x17); else { switch(byte) { case CMD_Y_CUR_STATUS: SetYCur(24); break; case CMD_GRAPH_50HZ: SetGraphics50Hz(); break; case CMD_GRAPH_60HZ: SetGraphics60Hz(); break; } } break; case CMD_RIGHT_MAR_L: SetRightMarginLow(byte); break; case CMD_RIGHT_MAR_H: SetRightMarginHigh(byte); break; } break; case CMD_11: if ((byte & 0xe0) == 0xe0) { SetReserved(byte); } else { switch (byte) { case CMD_GET_CHAR: GetChar(); break; case CMD_REQ_X_CUR: GetXCur(); break; case CMD_MRST: MasterReset(); break; case CMD_PRT_STAT: GetPrinterStatus(); break; case CMD_FILL_PREV: /* Reverts bits in the last char. For use in graphics mode. ROM location: 0636 */ FillMem(ReverseByte(last_char)); BlitScreen(); InputWord(0x01); break; case CMD_FILL_SPACE: /* ROM location: 063d */ FillMem(0x20); BlitScreen(); InputWord(0x01); break; case CMD_FILL_EOL: /* ROM location: 0641 */ FillMem(XEP80_ATARI_EOL); BlitScreen(); InputWord(0x01); break; case CMD_CLR_LIST: SetList(FALSE); break; case CMD_SET_LIST: SetList(TRUE); break; case CMD_SCR_NORMAL: SetOutputDevice(TRUE, FALSE); break; case CMD_SCR_BURST: SetOutputDevice(TRUE, TRUE); break; case CMD_SET_PRINT: SetOutputDevice(FALSE, TRUE); break; case CMD_CHAR_SET_A: SetCharSet(CHAR_SET_A); BlitScreen(); break; case CMD_CHAR_SET_B: SetCharSet(CHAR_SET_B); BlitScreen(); break; case CMD_CHAR_SET_INT: SetCharSet(CHAR_SET_INTERNAL); BlitScreen(); break; case CMD_TEXT_50HZ: SetText50Hz(); break; case CMD_CUR_OFF: SetCursorMode(FALSE, FALSE); break; case CMD_CUR_ON: SetCursorMode(TRUE, FALSE); break; case CMD_CUR_BLINK: SetCursorMode(TRUE, TRUE); break; case CMD_CUR_ST_LINE: SetXCurStart(); break; case CMD_SET_SCRL_WIN: SetScrollWindow(); break; case CMD_WHT_ON_BLK: SetInverse(FALSE); break; case CMD_BLK_ON_WHT: SetInverse(TRUE); break; default: /* All command left are 111000111 and 111001xxx, marked as Reserved. Actually they return values of various internal NS405 registers. Not currently emulated - implementation would require exact emulation of the whole NS405. 111000111: Get byte at CURS 111001000: Get INTR 111001001: Get PSW 111001010: Get PORT 111001011: Get TIMER 111001100: Get HPEN 111001101: Get VPEN 111001110: Get STAT 111001111: Get RCVR */ Log_print("XEP80 received not emulated command %03h", word); } } break; } } /* If it's data, then handle it as a character */ else { last_char = byte; ReceiveChar(byte); if (!burst_mode) SendCursorStatus(); } } /* ---------------- Other functions. ---------------- */ void XEP80_ChangeColors(void) { BlitScreen(); } int XEP80_ReadConfig(char *string, char *ptr) { if (strcmp(string, "XEP80_CHARSET") == 0) Util_strlcpy(charset_filename, ptr, sizeof(charset_filename)); else return FALSE; /* no match */ return TRUE; /* matched something */ } void XEP80_WriteConfig(FILE *fp) { fprintf(fp, "XEP80_CHARSET=%s\n", charset_filename); } int XEP80_SetEnabled(int value) { if (value && !XEP80_FONTS_inited && !XEP80_FONTS_InitFonts(charset_filename)) return FALSE; XEP80_enabled = value; return TRUE; } int XEP80_Initialise(int *argc, char *argv[]) { int i, j; int help_only = FALSE; for (i = j = 1; i < *argc; i++) { int i_a = (i + 1 < *argc); /* is argument available? */ int a_m = FALSE; /* error, argument missing! */ if (strcmp(argv[i], "-xep80") == 0) { XEP80_enabled = TRUE; } else if (strcmp(argv[i], "-xep80port") == 0) { if (i_a) { XEP80_port = Util_sscandec(argv[++i]); if (XEP80_port != 0 && XEP80_port != 1) { Log_print("Invalid XEP80 port - should be 0 or 1"); return FALSE; } } else a_m = TRUE; } else { if (strcmp(argv[i], "-help") == 0) { help_only = TRUE; Log_print("\t-xep80 Emulate the XEP80"); Log_print("\t-xep80port Use XEP80 on joystick port "); } argv[j++] = argv[i]; } if (a_m) { Log_print("Missing argument for '%s'", argv[i]); return FALSE; } } *argc = j; if (help_only) return TRUE; if (XEP80_enabled && !XEP80_SetEnabled(XEP80_enabled)) { XEP80_enabled = FALSE; Log_print("Couldn't load XEP80 charset image: %s", charset_filename); return FALSE; } start_trans_cpu_clock = ANTIC_CPU_CLOCK; ColdStart(); return TRUE; } UBYTE XEP80_GetBit(void) { UBYTE ret = 0xFF; int word_bit_num; int input_word; int input_word_num; /* Number of CPU ticks since start of word receiving. TODO: Avoid overflows in this value (minimal issue, since transmission rate is 15.7 kHz - way too low to allow for overflows). */ int num_ticks = (int)(ANTIC_CPU_CLOCK - start_trans_cpu_clock); int bit_no = num_ticks / ANTIC_LINE_C; /* If there is not input to be sent, just return */ if (input_count == 0 || num_ticks < 0) return ret; /* Figure out which word of the queue it is in based on bit */ input_word_num = (bit_no / 11); /* If it is greater than we have, then clear queue and return */ if (input_word_num >= input_count) { input_count = 0; return ret; } /* Get the word from the queue, and calculate which bit of the * word we are sending */ input_word = input_queue[input_word_num]; word_bit_num = bit_no % 11; /* Send the return value based on the bit */ switch(word_bit_num) { case 0: /* Start Bit - 0 */ ret = 0xFF & ~input_mask[XEP80_port]; break; case 1: /* 9 Data Bits */ case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: if (input_word & (1 << (word_bit_num-1))) ret = 0xFF; else ret = 0xFF & ~input_mask[XEP80_port]; break; case 10: /* Stop Bit - 1 */ ret = 0xFF; break; } return ret; } void XEP80_PutBit(UBYTE byte) { /* Number of CPU ticks since start of word receiving. TODO: Avoid overflows in this value (minimal issue, since transmission rate is 15.7 kHz - way too low to allow for overflows). */ int num_ticks = (int)(ANTIC_CPU_CLOCK - start_trans_cpu_clock); int bit_no = (num_ticks + ANTIC_LINE_C / 2) / ANTIC_LINE_C; byte &= output_mask[XEP80_port]; if (receiving) { switch (bit_no) { case 0: return; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: if (byte) output_word |= 1 << (bit_no - 1); break; case 10: /* Stop bit */ /* Clear any unread input from last command */ input_count = 0; receiving = FALSE; if (byte) { /* Set the start position of the next possible output byte, to 0.5 scanline after end of the current stop bit. This is not based on actual hardware - the delay was chosen to work with the Atari and the SpartaDOS X XEP80 drivers. Starting transmission immediately after end of the stop bit (prev_cpu_clock += 11 * ANTIC_LINE_C) would break the SDX driver. */ start_trans_cpu_clock += 11 * ANTIC_LINE_C + ANTIC_LINE_C / 2; /* Handle the new word */ /* Log_print("XEP80 <- %03x", output_word);*/ OutputWord(output_word); } return; default: /* Transmission timed out without receiving stop bit. */ receiving = FALSE; } } if (!receiving) { /* Either previous byte ended or no byte was received yet. */ if (!byte) { /* Start bit encountered. */ receiving = TRUE; start_trans_cpu_clock = ANTIC_CPU_CLOCK; output_word = 0; } } } void XEP80_StateSave(void) { StateSav_SaveINT(&XEP80_enabled, 1); if (XEP80_enabled) { int num_ticks = (int)(ANTIC_CPU_CLOCK - start_trans_cpu_clock); #if SUPPORTS_CHANGE_VIDEOMODE int show_xep80 = VIDEOMODE_80_column; #else int show_xep80 = 1; #endif /* SUPPORTS_CHANGE_VIDEOMODE */ StateSav_SaveINT(&XEP80_port, 1); StateSav_SaveINT(&show_xep80, 1); StateSav_SaveINT(&num_ticks, 1); StateSav_SaveINT(&output_word, 1); StateSav_SaveINT(&input_count, 1); StateSav_SaveINT(&receiving, 1); StateSav_SaveUWORD(input_queue, IN_QUEUE_SIZE); StateSav_SaveINT(&receiving, 1); StateSav_SaveUBYTE(&last_char, 1); StateSav_SaveINT(&xpos, 1); StateSav_SaveINT(&xscroll, 1); StateSav_SaveINT(&ypos, 1); StateSav_SaveINT(&cursor_x, 1); StateSav_SaveINT(&cursor_y, 1); StateSav_SaveINT(&curs, 1); StateSav_SaveINT(&old_xpos, 1); StateSav_SaveINT(&old_ypos, 1); StateSav_SaveINT(&lmargin, 1); StateSav_SaveINT(&rmargin, 1); StateSav_SaveUBYTE(&attrib_a, 1); StateSav_SaveUBYTE(&attrib_b, 1); StateSav_SaveINT(&list_mode, 1); StateSav_SaveINT(&escape_mode, 1); StateSav_SaveINT(&char_set, 1); StateSav_SaveINT(&cursor_on, 1); StateSav_SaveINT(&cursor_blink, 1); StateSav_SaveINT(&cursor_overwrite, 1); StateSav_SaveINT(&blink_reverse, 1); StateSav_SaveINT(&inverse_mode, 1); StateSav_SaveINT(&screen_output, 1); StateSav_SaveINT(&burst_mode, 1); StateSav_SaveINT(&graphics_mode, 1); StateSav_SaveINT(&pal_mode, 1); { int i; for (i = 0; i < XEP80_HEIGHT; ++i) { UBYTE ptr = ((int)(line_pointers[i] - video_ram)) / 0x100; StateSav_SaveUBYTE(&ptr, 1); } } StateSav_SaveUBYTE(video_ram, 8192); } } void XEP80_StateRead(void) { int local_xep80_enabled = FALSE; int local_show_xep80 = FALSE; /* test for end of file */ StateSav_ReadINT(&local_xep80_enabled, 1); if (!XEP80_SetEnabled(local_xep80_enabled)) XEP80_enabled = FALSE; if (local_xep80_enabled) { int num_ticks; StateSav_ReadINT(&XEP80_port, 1); StateSav_ReadINT(&local_show_xep80, 1); StateSav_ReadINT(&num_ticks, 1); start_trans_cpu_clock = ANTIC_CPU_CLOCK - num_ticks; StateSav_ReadINT(&output_word, 1); StateSav_ReadINT(&input_count, 1); StateSav_ReadINT(&receiving, 1); StateSav_ReadUWORD(input_queue, IN_QUEUE_SIZE); StateSav_ReadINT(&receiving, 1); StateSav_ReadUBYTE(&last_char, 1); StateSav_ReadINT(&xpos, 1); StateSav_ReadINT(&xscroll, 1); StateSav_ReadINT(&ypos, 1); StateSav_ReadINT(&cursor_x, 1); StateSav_ReadINT(&cursor_y, 1); StateSav_ReadINT(&curs, 1); StateSav_ReadINT(&old_xpos, 1); StateSav_ReadINT(&old_ypos, 1); StateSav_ReadINT(&lmargin, 1); StateSav_ReadINT(&rmargin, 1); StateSav_ReadUBYTE(&attrib_a, 1); UpdateAttributeBits(attrib_a, &font_a_index, &font_a_double, &font_a_blank, &font_a_blink); StateSav_ReadUBYTE(&attrib_b, 1); UpdateAttributeBits(attrib_b, &font_b_index, &font_b_double, &font_b_blank, &font_b_blink); StateSav_ReadINT(&list_mode, 1); StateSav_ReadINT(&escape_mode, 1); StateSav_ReadINT(&char_set, 1); StateSav_ReadINT(&cursor_on, 1); StateSav_ReadINT(&cursor_blink, 1); StateSav_ReadINT(&cursor_overwrite, 1); StateSav_ReadINT(&blink_reverse, 1); StateSav_ReadINT(&inverse_mode, 1); StateSav_ReadINT(&screen_output, 1); StateSav_ReadINT(&burst_mode, 1); StateSav_ReadINT(&graphics_mode, 1); StateSav_ReadINT(&pal_mode, 1); { int i; for (i = 0; i < XEP80_HEIGHT; ++i) { UBYTE ptr; StateSav_ReadUBYTE(&ptr, 1); line_pointers[i] = video_ram + 0x100*ptr; } } StateSav_ReadUBYTE(video_ram, 8192); UpdateTVSystem(); BlitScreen(); /* Clear the old text screen */ } #if SUPPORTS_CHANGE_VIDEOMODE VIDEOMODE_Set80Column(local_show_xep80); #endif } #endif /* XEP80 */ /* vim:ts=4:sw=4: */