/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * IBM PCjr video subsystem emulation * * * Authors: Sarah Walker, * Miran Grca, * Connor Hyde / starfrost * * Copyright 2008-2019 Sarah Walker. * Copyright 2016-2019 Miran Grca. * Copyright 2017-2019 Fred N. van Kempen. * Copyright 2025 starfrost */ #include #include #include #include #include #include <86box/86box.h> #include <86box/io.h> #include <86box/timer.h> #include <86box/mem.h> #include <86box/pic.h> #include <86box/pit.h> #include <86box/rom.h> #include <86box/device.h> #include <86box/video.h> #include <86box/vid_cga.h> #include <86box/vid_cga_comp.h> #include <86box/m_pcjr.h> static video_timings_t timing_dram = { VIDEO_BUS, 0, 0, 0, 0, 0, 0 }; /*No additional waitstates*/ static uint8_t crtcmask[32] = { 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f, 0x7f, 0x7f, 0xf3, 0x1f, 0x7f, 0x1f, 0x3f, 0xff, 0x3f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static void recalc_address(pcjr_t *pcjr) { uint8_t masked_memctrl = pcjr->memctrl; /* According to the Technical Reference, bits 2 and 5 are ignored if there is only 64k of RAM and there are only 4 pages. */ if (mem_size < 128) masked_memctrl &= ~0x24; if ((pcjr->memctrl & 0xc0) == 0xc0) { pcjr->vram = &ram[(masked_memctrl & 0x06) << 14]; pcjr->b8000 = &ram[(masked_memctrl & 0x30) << 11]; } else { pcjr->vram = &ram[(masked_memctrl & 0x07) << 14]; pcjr->b8000 = &ram[(masked_memctrl & 0x38) << 11]; } } void pcjr_recalc_timings(pcjr_t *pcjr) { double _dispontime; double _dispofftime; double disptime; if (pcjr->array[0] & 1) { disptime = pcjr->crtc[0] + 1; _dispontime = pcjr->crtc[1]; } else { disptime = (pcjr->crtc[0] + 1) << 1; _dispontime = pcjr->crtc[1] << 1; } _dispofftime = disptime - _dispontime; _dispontime *= CGACONST; _dispofftime *= CGACONST; pcjr->dispontime = (uint64_t) (_dispontime); pcjr->dispofftime = (uint64_t) (_dispofftime); } static int vid_get_h_overscan_size(pcjr_t *pcjr) { int ret; if (pcjr->array[0] & 1) ret = 128; else ret = 256; return ret; } static void vid_out(uint16_t addr, uint8_t val, void *priv) { pcjr_t *pcjr = (pcjr_t *) priv; uint8_t old; switch (addr) { case 0x3d0: case 0x3d2: case 0x3d4: case 0x3d6: pcjr->crtcreg = val & 0x1f; return; case 0x3d1: case 0x3d3: case 0x3d5: case 0x3d7: old = pcjr->crtc[pcjr->crtcreg]; pcjr->crtc[pcjr->crtcreg] = val & crtcmask[pcjr->crtcreg]; if (pcjr->crtcreg == 2) overscan_x = vid_get_h_overscan_size(pcjr); if (old != val) { if (pcjr->crtcreg < 0xe || pcjr->crtcreg > 0x10) { pcjr->fullchange = changeframecount; pcjr_recalc_timings(pcjr); } } return; case 0x3da: if (!pcjr->array_ff) pcjr->array_index = val & 0x1f; else { if (pcjr->array_index & 0x10) val &= 0x0f; pcjr->array[pcjr->array_index & 0x1f] = val; if (!(pcjr->array_index & 0x1f)) update_cga16_color(val); } pcjr->array_ff = !pcjr->array_ff; break; case 0x3df: pcjr->memctrl = val; pcjr->pa = val; /* The PCjr BIOS expects the value written to 3DF to then be readable from port 60, others it errors out with only 64k RAM set (but somehow, still works with 128k or more RAM). */ pcjr->addr_mode = val >> 6; recalc_address(pcjr); break; default: break; } } static uint8_t vid_in(uint16_t addr, void *priv) { pcjr_t *pcjr = (pcjr_t *) priv; uint8_t ret = 0xff; switch (addr) { case 0x3d0: case 0x3d2: case 0x3d4: case 0x3d6: ret = pcjr->crtcreg; break; case 0x3d1: case 0x3d3: case 0x3d5: case 0x3d7: ret = pcjr->crtc[pcjr->crtcreg]; break; case 0x3da: pcjr->array_ff = 0; pcjr->status ^= 0x10; ret = pcjr->status; break; default: break; } return ret; } static void vid_write(uint32_t addr, uint8_t val, void *priv) { pcjr_t *pcjr = (pcjr_t *) priv; if (pcjr->memctrl == -1) return; pcjr->b8000[addr & 0x3fff] = val; } static uint8_t vid_read(uint32_t addr, void *priv) { const pcjr_t *pcjr = (pcjr_t *) priv; if (pcjr->memctrl == -1) return 0xff; return (pcjr->b8000[addr & 0x3fff]); } static int vid_get_h_overscan_delta(pcjr_t *pcjr) { int def; int coef; int ret; switch ((pcjr->array[0] & 0x13) | ((pcjr->array[3] & 0x08) << 5)) { case 0x13: /*320x200x16*/ def = 0x56; coef = 8; break; case 0x12: /*160x200x16*/ def = 0x2c; /* I'm going to assume a datasheet erratum here. */ coef = 16; break; case 0x03: /*640x200x4*/ def = 0x56; coef = 8; break; case 0x01: /*80 column text*/ def = 0x5a; coef = 8; break; case 0x00: /*40 column text*/ default: def = 0x2c; coef = 16; break; case 0x02: /*320x200x4*/ def = 0x2b; coef = 16; break; case 0x102: /*640x200x2*/ def = 0x2b; coef = 16; break; } ret = pcjr->crtc[0x02] - def; if (ret < -8) ret = -8; if (ret > 8) ret = 8; return ret * coef; } static void vid_blit_h_overscan(pcjr_t *pcjr) { int cols = (pcjr->array[2] & 0xf) + 16;; int y0 = pcjr->firstline << 1; int y = (pcjr->lastline << 1) + 16; int ho_s = vid_get_h_overscan_size(pcjr); int i; int x; if (pcjr->array[0] & 1) x = (pcjr->crtc[1] << 3) + ho_s; else x = (pcjr->crtc[1] << 4) + ho_s; for (i = 0; i < 16; i++) { hline(buffer32, 0, y0 + i, x, cols); hline(buffer32, 0, y + i, x, cols); if (pcjr->composite) { Composite_Process(pcjr->array[0], 0, x >> 2, buffer32->line[y0 + i]); Composite_Process(pcjr->array[0], 0, x >> 2, buffer32->line[y + i]); } else { video_process_8(x, y0 + i); video_process_8(x, y + i); } } } static void vid_poll(void *priv) { pcjr_t *pcjr = (pcjr_t *) priv; uint16_t cursoraddr = (pcjr->crtc[15] | (pcjr->crtc[14] << 8)) & 0x3fff; int drawcursor; int x; int xs_temp; int ys_temp; int oldvc; uint8_t chr; uint8_t attr; uint16_t dat; int cols[4]; int scanline_old; int l = (pcjr->displine << 1) + 16; int ho_s = vid_get_h_overscan_size(pcjr); int ho_d = vid_get_h_overscan_delta(pcjr) + (ho_s / 2); if (!pcjr->linepos) { timer_advance_u64(&pcjr->timer, pcjr->dispofftime); pcjr->status &= ~1; pcjr->linepos = 1; scanline_old = pcjr->scanline; if ((pcjr->crtc[8] & 3) == 3) pcjr->scanline = (pcjr->scanline << 1) & 7; if (pcjr->dispon) { uint16_t offset = 0; uint16_t mask = 0x1fff; if (pcjr->displine < pcjr->firstline) { pcjr->firstline = pcjr->displine; video_wait_for_buffer(); } pcjr->lastline = pcjr->displine; cols[0] = (pcjr->array[2] & 0xf) + 16; if (pcjr->array[0] & 1) { hline(buffer32, 0, l, (pcjr->crtc[1] << 3) + ho_s, cols[0]); hline(buffer32, 0, l + 1, (pcjr->crtc[1] << 3) + ho_s, cols[0]); } else { hline(buffer32, 0, l, (pcjr->crtc[1] << 4) + ho_s, cols[0]); hline(buffer32, 0, l + 1, (pcjr->crtc[1] << 4) + ho_s, cols[0]); } switch (pcjr->addr_mode) { case 0: /*Alpha*/ offset = 0; mask = 0x3fff; break; case 1: /*Low resolution graphics*/ offset = (pcjr->scanline & 1) * 0x2000; break; case 3: /*High resolution graphics*/ offset = (pcjr->scanline & 3) * 0x2000; break; default: break; } switch ((pcjr->array[0] & 0x13) | ((pcjr->array[3] & 0x08) << 5)) { case 0x13: /*320x200x16*/ for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 3) + ho_d; dat = (pcjr->vram[((pcjr->memaddr << 1) & mask) + offset] << 8) | pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1]; pcjr->memaddr++; buffer32->line[l][ef_x] = buffer32->line[l][ef_x + 1] = buffer32->line[l + 1][ef_x] = buffer32->line[l + 1][ef_x + 1] = pcjr->array[((dat >> 12) & pcjr->array[1] & 0x0f) + 16] + 16; buffer32->line[l][ef_x + 2] = buffer32->line[l][ef_x + 3] = buffer32->line[l + 1][ef_x + 2] = buffer32->line[l + 1][ef_x + 3] = pcjr->array[((dat >> 8) & pcjr->array[1] & 0x0f) + 16] + 16; buffer32->line[l][ef_x + 4] = buffer32->line[l][ef_x + 5] = buffer32->line[l + 1][ef_x + 4] = buffer32->line[l + 1][ef_x + 5] = pcjr->array[((dat >> 4) & pcjr->array[1] & 0x0f) + 16] + 16; buffer32->line[l][ef_x + 6] = buffer32->line[l][ef_x + 7] = buffer32->line[l + 1][ef_x + 6] = buffer32->line[l + 1][ef_x + 7] = pcjr->array[(dat & pcjr->array[1] & 0x0f) + 16] + 16; } break; case 0x12: /*160x200x16*/ for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 4) + ho_d; dat = (pcjr->vram[((pcjr->memaddr << 1) & mask) + offset] << 8) | pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1]; pcjr->memaddr++; buffer32->line[l][ef_x] = buffer32->line[l][ef_x + 1] = buffer32->line[l][ef_x + 2] = buffer32->line[l][ef_x + 3] = buffer32->line[l + 1][ef_x] = buffer32->line[l + 1][ef_x + 1] = buffer32->line[l + 1][ef_x + 2] = buffer32->line[l + 1][ef_x + 3] = pcjr->array[((dat >> 12) & pcjr->array[1] & 0x0f) + 16] + 16; buffer32->line[l][ef_x + 4] = buffer32->line[l][ef_x + 5] = buffer32->line[l][ef_x + 6] = buffer32->line[l][ef_x + 7] = buffer32->line[l + 1][ef_x + 4] = buffer32->line[l + 1][ef_x + 5] = buffer32->line[l + 1][ef_x + 6] = buffer32->line[l + 1][ef_x + 7] = pcjr->array[((dat >> 8) & pcjr->array[1] & 0x0f) + 16] + 16; buffer32->line[l][ef_x + 8] = buffer32->line[l][ef_x + 9] = buffer32->line[l][ef_x + 10] = buffer32->line[l][ef_x + 11] = buffer32->line[l + 1][ef_x + 8] = buffer32->line[l + 1][ef_x + 9] = buffer32->line[l + 1][ef_x + 10] = buffer32->line[l + 1][ef_x + 11] = pcjr->array[((dat >> 4) & pcjr->array[1] & 0x0f) + 16] + 16; buffer32->line[l][ef_x + 12] = buffer32->line[l][ef_x + 13] = buffer32->line[l][ef_x + 14] = buffer32->line[l][ef_x + 15] = buffer32->line[l + 1][ef_x + 12] = buffer32->line[l + 1][ef_x + 13] = buffer32->line[l + 1][ef_x + 14] = buffer32->line[l + 1][ef_x + 15] = pcjr->array[(dat & pcjr->array[1] & 0x0f) + 16] + 16; } break; case 0x03: /*640x200x4*/ for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 3) + ho_d; dat = (pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1] << 8) | pcjr->vram[((pcjr->memaddr << 1) & mask) + offset]; pcjr->memaddr++; for (uint8_t c = 0; c < 8; c++) { chr = (dat >> 7) & 1; chr |= ((dat >> 14) & 2); buffer32->line[l][ef_x + c] = buffer32->line[l + 1][ef_x + c] = pcjr->array[(chr & pcjr->array[1] & 0x0f) + 16] + 16; dat <<= 1; } } break; case 0x01: /*80 column text*/ for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 3) + ho_d; chr = pcjr->vram[((pcjr->memaddr << 1) & mask) + offset]; attr = pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1]; drawcursor = ((pcjr->memaddr == cursoraddr) && pcjr->cursorvisible && pcjr->cursoron); if (pcjr->array[3] & 4) { cols[1] = pcjr->array[((attr & 15) & pcjr->array[1] & 0x0f) + 16] + 16; cols[0] = pcjr->array[(((attr >> 4) & 7) & pcjr->array[1] & 0x0f) + 16] + 16; if ((pcjr->blink & 16) && (attr & 0x80) && !drawcursor) cols[1] = cols[0]; } else { cols[1] = pcjr->array[((attr & 15) & pcjr->array[1] & 0x0f) + 16] + 16; cols[0] = pcjr->array[((attr >> 4) & pcjr->array[1] & 0x0f) + 16] + 16; } if (pcjr->scanline & 8) for (uint8_t c = 0; c < 8; c++) buffer32->line[l][ef_x + c] = buffer32->line[l + 1][ef_x + c] = cols[0]; else for (uint8_t c = 0; c < 8; c++) buffer32->line[l][ef_x + c] = buffer32->line[l + 1][ef_x + c] = cols[(fontdat[chr][pcjr->scanline & 7] & (1 << (c ^ 7))) ? 1 : 0]; if (drawcursor) for (uint8_t c = 0; c < 8; c++) { buffer32->line[l][ef_x + c] ^= 15; buffer32->line[l + 1][ef_x + c] ^= 15; } pcjr->memaddr++; } break; case 0x00: /*40 column text*/ for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 4) + ho_d; chr = pcjr->vram[((pcjr->memaddr << 1) & mask) + offset]; attr = pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1]; drawcursor = ((pcjr->memaddr == cursoraddr) && pcjr->cursorvisible && pcjr->cursoron); if (pcjr->array[3] & 4) { cols[1] = pcjr->array[((attr & 15) & pcjr->array[1] & 0x0f) + 16] + 16; cols[0] = pcjr->array[(((attr >> 4) & 7) & pcjr->array[1] & 0x0f) + 16] + 16; if ((pcjr->blink & 16) && (attr & 0x80) && !drawcursor) cols[1] = cols[0]; } else { cols[1] = pcjr->array[((attr & 15) & pcjr->array[1] & 0x0f) + 16] + 16; cols[0] = pcjr->array[((attr >> 4) & pcjr->array[1] & 0x0f) + 16] + 16; } pcjr->memaddr++; if (pcjr->scanline & 8) for (uint8_t c = 0; c < 8; c++) buffer32->line[l][ef_x + (c << 1)] = buffer32->line[l][ef_x + (c << 1) + 1] = buffer32->line[l + 1][ef_x + (c << 1)] = buffer32->line[l + 1][ef_x + (c << 1) + 1] = cols[0]; else for (uint8_t c = 0; c < 8; c++) buffer32->line[l][ef_x + (c << 1)] = buffer32->line[l][ef_x + (c << 1) + 1] = buffer32->line[l + 1][ef_x + (c << 1)] = buffer32->line[l + 1][ef_x + (c << 1) + 1] = cols[(fontdat[chr][pcjr->scanline & 7] & (1 << (c ^ 7))) ? 1 : 0]; if (drawcursor) for (uint8_t c = 0; c < 16; c++) { buffer32->line[l][ef_x + c] ^= 15; buffer32->line[l + 1][ef_x + c] ^= 15; } } break; case 0x02: /*320x200x4*/ cols[0] = pcjr->array[0 + 16] + 16; cols[1] = pcjr->array[1 + 16] + 16; cols[2] = pcjr->array[2 + 16] + 16; cols[3] = pcjr->array[3 + 16] + 16; for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 4) + ho_d; dat = (pcjr->vram[((pcjr->memaddr << 1) & mask) + offset] << 8) | pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1]; pcjr->memaddr++; for (uint8_t c = 0; c < 8; c++) { buffer32->line[l][ef_x + (c << 1)] = buffer32->line[l][ef_x + (c << 1) + 1] = buffer32->line[l + 1][ef_x + (c << 1)] = buffer32->line[l + 1][ef_x + (c << 1) + 1] = cols[dat >> 14]; dat <<= 2; } } break; case 0x102: /*640x200x2*/ cols[0] = pcjr->array[0 + 16] + 16; cols[1] = pcjr->array[1 + 16] + 16; for (x = 0; x < pcjr->crtc[1]; x++) { int ef_x = (x << 4) + ho_d; dat = (pcjr->vram[((pcjr->memaddr << 1) & mask) + offset] << 8) | pcjr->vram[((pcjr->memaddr << 1) & mask) + offset + 1]; pcjr->memaddr++; for (uint8_t c = 0; c < 16; c++) { buffer32->line[l][ef_x + c] = buffer32->line[l + 1][ef_x + c] = cols[dat >> 15]; dat <<= 1; } } break; default: break; } } else { if (pcjr->array[3] & 4) { if (pcjr->array[0] & 1) { hline(buffer32, 0, l, (pcjr->crtc[1] << 3) + ho_s, (pcjr->array[2] & 0xf) + 16); hline(buffer32, 0, l + 1, (pcjr->crtc[1] << 3) + ho_s, (pcjr->array[2] & 0xf) + 16); } else { hline(buffer32, 0, l, (pcjr->crtc[1] << 4) + ho_s, (pcjr->array[2] & 0xf) + 16); hline(buffer32, 0, l + 1, (pcjr->crtc[1] << 4) + ho_s, (pcjr->array[2] & 0xf) + 16); } } else { cols[0] = pcjr->array[0 + 16] + 16; if (pcjr->array[0] & 1) { hline(buffer32, 0, l, (pcjr->crtc[1] << 3) + ho_s, cols[0]); hline(buffer32, 0, l + 1, (pcjr->crtc[1] << 3) + ho_s, cols[0]); } else { hline(buffer32, 0, l, (pcjr->crtc[1] << 4) + ho_s, cols[0]); hline(buffer32, 0, l + 1, (pcjr->crtc[1] << 4) + ho_s, cols[0]); } } } if (pcjr->array[0] & 1) x = (pcjr->crtc[1] << 3) + ho_s; else x = (pcjr->crtc[1] << 4) + ho_s; if (pcjr->composite) { Composite_Process(pcjr->array[0], 0, x >> 2, buffer32->line[l]); Composite_Process(pcjr->array[0], 0, x >> 2, buffer32->line[l + 1]); } else { video_process_8(x, l); video_process_8(x, l + 1); } pcjr->scanline = scanline_old; if (pcjr->vc == pcjr->crtc[7] && !pcjr->scanline) { pcjr->status |= 8; } pcjr->displine++; if (pcjr->displine >= 360) pcjr->displine = 0; } else { timer_advance_u64(&pcjr->timer, pcjr->dispontime); if (pcjr->dispon) pcjr->status |= 1; pcjr->linepos = 0; if (pcjr->vsynctime) { pcjr->vsynctime--; if (!pcjr->vsynctime) { pcjr->status &= ~8; } } if (pcjr->scanline == (pcjr->crtc[11] & 31) || ((pcjr->crtc[8] & 3) == 3 && pcjr->scanline == ((pcjr->crtc[11] & 31) >> 1))) { pcjr->cursorvisible = 0; } if (pcjr->vadj) { pcjr->scanline++; pcjr->scanline &= 31; pcjr->memaddr = pcjr->memaddr_backup; pcjr->vadj--; if (!pcjr->vadj) { pcjr->dispon = 1; pcjr->memaddr = pcjr->memaddr_backup = (pcjr->crtc[13] | (pcjr->crtc[12] << 8)) & 0x3fff; pcjr->scanline = 0; } } else if (pcjr->scanline == pcjr->crtc[9] || ((pcjr->crtc[8] & 3) == 3 && pcjr->scanline == (pcjr->crtc[9] >> 1))) { pcjr->memaddr_backup = pcjr->memaddr; pcjr->scanline = 0; oldvc = pcjr->vc; pcjr->vc++; pcjr->vc &= 127; if (pcjr->vc == pcjr->crtc[6]) pcjr->dispon = 0; if (oldvc == pcjr->crtc[4]) { pcjr->vc = 0; pcjr->vadj = pcjr->crtc[5]; if (!pcjr->vadj) pcjr->dispon = 1; if (!pcjr->vadj) pcjr->memaddr = pcjr->memaddr_backup = (pcjr->crtc[13] | (pcjr->crtc[12] << 8)) & 0x3fff; if ((pcjr->crtc[10] & 0x60) == 0x20) pcjr->cursoron = 0; else pcjr->cursoron = pcjr->blink & 16; } if (pcjr->vc == pcjr->crtc[7]) { pcjr->dispon = 0; pcjr->displine = 0; pcjr->vsynctime = 16; picint(1 << 5); if (pcjr->crtc[7]) { if (pcjr->array[0] & 1) x = (pcjr->crtc[1] << 3) + ho_s; else x = (pcjr->crtc[1] << 4) + ho_s; pcjr->lastline++; xs_temp = x; ys_temp = (pcjr->lastline - pcjr->firstline) << 1; if ((xs_temp > 0) && (ys_temp > 0)) { int actual_ys = ys_temp; if (xs_temp < 64) xs_temp = 656; if (ys_temp < 32) ys_temp = 400; if (!enable_overscan) xs_temp -= ho_s; if ((xs_temp != xsize) || (ys_temp != ysize) || video_force_resize_get()) { xsize = xs_temp; ysize = ys_temp; set_screen_size(xsize, ysize + (enable_overscan ? 32 : 0)); if (video_force_resize_get()) video_force_resize_set(0); } vid_blit_h_overscan(pcjr); if (enable_overscan) { video_blit_memtoscreen(0, pcjr->firstline << 1, xsize, actual_ys + 32); } else if (pcjr->apply_hd) { video_blit_memtoscreen(ho_s / 2, (pcjr->firstline << 1) + 16, xsize, actual_ys); } else { video_blit_memtoscreen(ho_d, (pcjr->firstline << 1) + 16, xsize, actual_ys); } } frames++; video_res_x = xsize; video_res_y = ysize; } pcjr->firstline = 1000; pcjr->lastline = 0; pcjr->blink++; } } else { pcjr->scanline++; pcjr->scanline &= 31; pcjr->memaddr = pcjr->memaddr_backup; } if (pcjr->scanline == (pcjr->crtc[10] & 31) || ((pcjr->crtc[8] & 3) == 3 && pcjr->scanline == ((pcjr->crtc[10] & 31) >> 1))) pcjr->cursorvisible = 1; } } void pcjr_vid_init(pcjr_t *pcjr) { int display_type; video_inform(VIDEO_FLAG_TYPE_CGA, &timing_dram); pcjr->memctrl = -1; if (mem_size < 128) pcjr->memctrl &= ~0x24; display_type = device_get_config_int("display_type"); pcjr->composite = (display_type == PCJR_COMPOSITE); pcjr->apply_hd = device_get_config_int("apply_hd"); overscan_x = 256; overscan_y = 32; mem_mapping_add(&pcjr->mapping, 0xb8000, 0x08000, vid_read, NULL, NULL, vid_write, NULL, NULL, NULL, 0, pcjr); io_sethandler(0x03d0, 16, vid_in, NULL, NULL, vid_out, NULL, NULL, pcjr); timer_add(&pcjr->timer, vid_poll, pcjr, 1); if (pcjr->composite) cga_palette = 0; else cga_palette = (display_type << 1); cgapal_rebuild(); }