#include #include #include /*** V I D E O ***/ #define setMode(hexval) asm { mov ax, hexval; int 10h } #define setVGAMode() setMode(0013h) #define setEGAMode() setMode(000Dh) #define setTextMode() setMode(0003h) #define REG_AC 0x03c0 #define REG_TS 0x03c4 #define REG_GDC 0x03ce #define REG_CRTC 0x03d4 #define PLANE_B 0x00 #define PLANE_G 0x01 #define PLANE_R 0x02 #define PLANE_I 0x03 #define setPlane(p) outport(REG_TS, 2 | (0x100 << p)) #define setAllPlanes() outport(REG_TS, 0x0f02) #define VID ((volatile char far *)MK_FP(0xa000, 0)) #define WVID ((volatile int far *)MK_FP(0xa000, 0)) void vid_cleanup() { setTextMode(); } #define setWriteMode(m) outport(REG_GDC, 0x05 | m << 8) void setSplitScreen(unsigned int y) { int val; outport(REG_CRTC, 0x18 | (y << 8)); outp(REG_CRTC, 7); val = inp(REG_CRTC + 1); val &= ~0x10; val |= (y & 0x100) >> 4; outp(REG_CRTC + 1, val); outp(REG_CRTC, 9); val = inp(REG_CRTC + 1); val &= ~0x40; outp(REG_CRTC + 1, val); } void unsetSplitScreen() { outport(REG_CRTC, 0xff18); outport(REG_CRTC, 0x1107); outport(REG_CRTC, 0x0f09); } #define flipPage(p) outport(REG_CRTC, 0x0c | (p << 8)) void setDisplayOffset(unsigned int offset) { outport(REG_CRTC, 0x0c | (offset & 0xff00)); outport(REG_CRTC, 0x0d | (offset << 8)); } void setHorizontalPan(int offset) { inp(0x3da); // INPUT_STATUS_1? outp(REG_AC, 0x13 | 0x20); outp(REG_AC, offset); } #define setLogicalWidth(w) outport(REG_CRTC, 0x13 | (w << 8)) /*** K E Y B O A R D ***/ #define KBD_INT 0x09 void interrupt (*oldKbdISR)() = NULL; void kbd_cleanup() { if (oldKbdISR != NULL) { setvect(KBD_INT, oldKbdISR); oldKbdISR = NULL; } } volatile char keybuf[128]; volatile char kbd_triggered = 0; void interrupt kbd_isr() { unsigned char raw; char ctl; asm sti; raw = inp(0x60); ctl = inp(0x61) | 0x82; outp(0x61, ctl); outp(0x61, ctl & 0x7f); outp(0x20, 0x20); if (raw & 0x80) { keybuf[raw & 0x7f] = 0; } else { keybuf[raw] = 1; } kbd_triggered = raw; } #define K_ESC 1 #define K_1 2 #define K_2 3 #define K_3 4 #define K_4 5 #define K_5 6 #define K_6 7 #define K_7 8 #define K_8 9 #define K_9 10 #define K_0 11 #define K_MINUS 12 #define K_EQUAL 13 #define K_BKSP 14 #define K_TAB 15 #define K_Q 16 #define K_W 17 #define K_E 18 #define K_R 19 #define K_T 20 #define K_Y 21 #define K_U 22 #define K_I 23 #define K_O 24 #define K_P 25 #define K_LBRK 26 #define K_RBRK 27 #define K_ENTER 28 #define K_CTRL 29 #define K_A 30 #define K_S 31 #define K_D 32 #define K_F 33 #define K_G 34 #define K_H 35 #define K_J 36 #define K_K 37 #define K_L 38 #define K_SEMI 39 #define K_APOS 40 #define K_TILDE 41 #define K_LSHFT 42 #define K_BSLSH 43 #define K_Z 44 #define K_X 45 #define K_C 46 #define K_V 47 #define K_B 48 #define K_N 49 #define K_M 50 #define K_COMMA 51 #define K_DOT 52 #define K_SLASH 53 #define K_RSHFT 54 #define K_PSCRN 55 #define K_ALT 56 #define K_SPACE 57 #define K_CAPS 58 #define K_F1 59 #define K_F2 60 #define K_F3 61 #define K_F4 62 #define K_F5 63 #define K_F6 64 #define K_F7 65 #define K_F8 66 #define K_F9 67 #define K_F10 68 #define K_NUMLK 69 #define K_SCRL 70 #define K_HOME 71 #define K_UP 72 #define K_PGUP 73 #define K_NDASH 74 #define K_LEFT 75 #define K_CENT 76 #define K_RIGHT 77 #define K_NPLUS 78 #define K_END 79 #define K_DOWN 80 #define K_PGDN 81 #define K_INS 82 #define K_DEL 83 #define K_F11 87 #define K_F12 88 #define keyPressed(k) keybuf[k] unsigned char kbd_wait() { kbd_triggered = 0; while (!kbd_triggered) {} return kbd_triggered; } void kbd_init() { if (oldKbdISR == NULL) { memset(keybuf, 0, 128); oldKbdISR = getvect(KBD_INT); setvect(KBD_INT, kbd_isr); atexit(kbd_cleanup); } } /*** M O U S E ***/ struct { unsigned int x; unsigned int y; unsigned int buttons; } MOUSE; void far mouse_callback() { asm { mov ax, DGROUP mov ds, ax shr cx, 1 mov MOUSE.x, cx mov MOUSE.y, dx mov MOUSE.buttons, bx } } void mouse_cleanup() { //uninstall handler asm { mov ax, 0ch mov dx, 0 mov es, dx mov cx, 0 int 33h xor ax, ax int 33h } } void mouse_init() { unsigned seg_mouse_callback = FP_SEG(mouse_callback); unsigned off_mouse_callback = FP_OFF(mouse_callback); unsigned int result; asm { xor ax, ax int 33h mov result, ax } if (result == 0) { printf("Mouse driver not installed\n"); exit(1); } atexit(mouse_cleanup); asm { mov ax, seg_mouse_callback mov es, ax mov dx, off_mouse_callback mov ax, 0ch mov cx, 1fh int 33h } } #define mouse_hide() asm { mov ax, 02h; int 33h } #define mouse_show() asm { mov ax, 01h; int 33h } /*** T I F F ***/ typedef struct { unsigned int endian; unsigned int version; unsigned long ifdOffset; } TifHeader_t; #define TIF_WIDTH 256 #define TIF_HEIGHT 257 #define TIF_BITSPERSAMPLE 258 #define TIF_COMPRESSION 259 #define TIF_STRIPOFFSETS 273 #define TIF_ROWSPERSTRIP 278 typedef struct { unsigned int id; unsigned int dataType; unsigned long dataCount; unsigned long dataOffset; } TifTag_t; typedef struct { unsigned int width; unsigned int height; unsigned long rowsPerStrip; unsigned long stripCount; unsigned long stripOffsets; } TifImageMeta_t; TifImageMeta_t tifLoadMeta(FILE *f) { TifImageMeta_t meta = {0, 0, 0, 0, 0}; TifHeader_t header; TifTag_t tag; unsigned int i, tagCount; fseek(f, 0, SEEK_SET); fread(&header, 8, 1, f); if (header.endian != 0x4949 || header.version != 0x2a) { goto fail; } fseek(f, header.ifdOffset, SEEK_SET); fread(&tagCount, 2, 1, f); for (i = 0; i < tagCount; i ++) { fread(&tag, 12, 1, f); if (tag.id == TIF_WIDTH) { meta.width = tag.dataOffset; } else if (tag.id == TIF_HEIGHT) { meta.height = tag.dataOffset; } else if (tag.id == TIF_BITSPERSAMPLE) { if (tag.dataOffset != 4) goto fail; } else if (tag.id == TIF_COMPRESSION) { if (tag.dataOffset != 1) goto fail; } else if (tag.id == TIF_STRIPOFFSETS) { meta.stripCount = tag.dataCount; meta.stripOffsets = tag.dataOffset; } else if (tag.id == TIF_ROWSPERSTRIP) { meta.rowsPerStrip = tag.dataOffset; } } return meta; fail: meta.stripCount = 0; return meta; } #define MAX_WIDTH 320 int tifLoadEGA(FILE *f, TifImageMeta_t meta, unsigned int vidOffset, int maxY, unsigned int w) { int istrip; int irow; int ipixelpair; int y = 0; unsigned long offset; unsigned char rowData[MAX_WIDTH >> 1]; volatile unsigned char far *out = &VID[vidOffset]; unsigned char b, g, r, i; if (meta.width > MAX_WIDTH || (meta.width % 16) != 0) { return 0; } setWriteMode(0); for (istrip = 0; istrip < meta.stripCount; istrip ++) { fseek(f, meta.stripOffsets + (istrip << 2), SEEK_SET); fread(&offset, 4, 1, f); fseek(f, offset, SEEK_SET); for (irow = 0; irow < meta.rowsPerStrip; irow ++) { int ipixelpairLim = meta.width >> 1; fread(rowData, 1, ipixelpairLim, f); b = g = r = i = 0; for (ipixelpair = 0; ipixelpair < ipixelpairLim; ipixelpair ++) { unsigned char pixelpair = rowData[ipixelpair]; int bpair = (pixelpair & 0x01) | (pixelpair & 0x10) >> 3; int gpair = (pixelpair & 0x02) >> 1 | (pixelpair & 0x20) >> 4; int rpair = (pixelpair & 0x04) >> 2 | (pixelpair & 0x40) >> 5; int ipair = (pixelpair & 0x08) >> 3 | (pixelpair & 0x80) >> 6; int shift = (3 - (ipixelpair % 4)) << 1; b |= bpair << shift; g |= gpair << shift; r |= rpair << shift; i |= ipair << shift; if (shift == 0 || ipixelpair == ipixelpairLim - 1) { // todo: use write mode 2, this is slooww setPlane(PLANE_B); *out = b; setPlane(PLANE_R); *out = r; setPlane(PLANE_G); *out = g; setPlane(PLANE_I); *out = i; out ++; b = g = r = i = 0; } } y++; if (y == maxY) { return y; } out += (w - meta.width) >> 3; } } return y; } int tifLoad(FILE *f, TifImageMeta_t meta, unsigned int *planeBuf, int maxY, int yRepeat, int planes) { int istrip; int irow; int ipixelpair; int y = 0; unsigned long offset; unsigned char rowData[MAX_WIDTH >> 1]; unsigned int planeStride = (meta.width >> 4) * yRepeat; unsigned int *bp = planeBuf; unsigned int *gp = bp + planeStride; unsigned int *rp = gp + planeStride; unsigned int *ip = rp + planeStride; unsigned int *mp = ip + planeStride; unsigned int bv, gv, rv, iv; if (meta.width > MAX_WIDTH || (meta.width % 16) != 0 || planes < 4 || planes > 5) { return 0; } for (istrip = 0; istrip < meta.stripCount; istrip ++) { fseek(f, meta.stripOffsets + (istrip << 2), SEEK_SET); fread(&offset, 4, 1, f); fseek(f, offset, SEEK_SET); for (irow = 0; irow < meta.rowsPerStrip; irow ++) { int ipixelpairLim = meta.width >> 1; fread(rowData, 1, ipixelpairLim, f); bv = gv = rv = iv = 0; for (ipixelpair = 0; ipixelpair < ipixelpairLim; ipixelpair ++) { unsigned char pixelpair = rowData[ipixelpair]; int bpair = (pixelpair & 0x01) | (pixelpair & 0x10) >> 3; int gpair = (pixelpair & 0x02) >> 1 | (pixelpair & 0x20) >> 4; int rpair = (pixelpair & 0x04) >> 2 | (pixelpair & 0x40) >> 5; int ipair = (pixelpair & 0x08) >> 3 | (pixelpair & 0x80) >> 6; int shift = (7 - (ipixelpair % 8)) << 1; bv |= bpair << shift; gv |= gpair << shift; rv |= rpair << shift; iv |= ipair << shift; if (shift == 0 || ipixelpair == ipixelpairLim - 1) { *bp++ = bv; *gp++ = gv; *rp++ = rv; *ip++ = iv; if (planes == 5) { iv = ~(bv & gv & rv & iv); *mp++ = iv; } bv = gv = rv = iv = 0; } } y++; if (y == maxY) { return y; } if (y % yRepeat == 0) { bp += planeStride * (planes - 1); gp += planeStride * (planes - 1); rp += planeStride * (planes - 1); ip += planeStride * (planes - 1); mp += planeStride * (planes - 1); } } } return y; } /*** T I L E S ***/ // Tiles are 16x16 bitmaps, stored as arrays of words. // Each tile has 4 or 5 planes (depending on whether it is a tile or sprite) // which are stored adjacant to each other; ie. a 16-word array of blue, // followed by a 16-word array of green, etc. // Tiles in RAM are stored byte-swapped to aid in fast bit-shifting, and must // be byte-swapped before being written to video memory. // Because bit-shifting operations happen on little-endian words: // 01234567 89ABCDEF << 3 => 34567XXX BCDEF012 // which is wrong. So instead we do: // 89ABCDEF 01234567 << 3 => BCDEFXXX 3456789A byteswap => 3456789A BCDEFXXX #define PAGE_TILES_W 21 #define PAGE_TILES_H 12 #define PAGE_TILES_COUNT (PAGE_TILES_H * PAGE_TILES_W) #define PAGE_STRIDE (PAGE_TILES_W << 1) void tile_init() { setLogicalWidth(PAGE_STRIDE >> 1); } void blitTile(unsigned int offsetFrom, unsigned int offsetTo) { int y; for (y = 0; y < 16; y ++) { VID[offsetTo] = VID[offsetFrom ++]; VID[offsetTo + 1] = VID[offsetFrom ++]; offsetTo += PAGE_STRIDE; } } #define D_NOTHING 0x80 #define D_BGTILE 0x81 #define isBufIndex(d) (!((d) & 0x80)) #define NUM_BUFFERS 32 #define nextBufferIndex(i) ((i + 1) % 32) #define BUF_WSTRIDE 16 #define BUF_WSIZE (BUF_WSTRIDE * 4) typedef struct { unsigned int w; unsigned int h; int scrollX; int scrollY; unsigned int pageOffset[2]; unsigned char dirty[2][PAGE_TILES_COUNT]; unsigned int tilesOffset; unsigned int *memTiles; unsigned char *map; unsigned int buffer[NUM_BUFFERS][BUF_WSIZE]; unsigned int bufferOffset[NUM_BUFFERS]; unsigned char currentPage; unsigned char nextBuffer; unsigned char firstBuffer; } TiledScreen_t; TiledScreen_t screen = { 0, 0, 0, 0, { 0x0400, 0x2400 }, 0, 0, NULL, NULL, 0, 0, 0, 0, 0 }; void loadTiles(unsigned int tilesOffset, unsigned int *memTiles) { screen.tilesOffset = tilesOffset; screen.memTiles = memTiles; } void loadMap(unsigned char *map, unsigned int w, unsigned int h) { screen.map = map; screen.w = w; screen.h = h; memset(screen.dirty, D_BGTILE, PAGE_TILES_COUNT * 2); } int prepareBuffer(int pageX, int pageY) { unsigned char *dirty = &screen.dirty[screen.currentPage][pageX + (pageY * PAGE_TILES_W)]; if (!isBufIndex(*dirty)) { unsigned int startX = screen.scrollX >> 4; unsigned int startY = screen.scrollY >> 4; unsigned char tile = screen.map[startX + pageX + ((startY + pageY) * screen.w)]; unsigned char ibuffer = screen.nextBuffer; screen.nextBuffer = nextBufferIndex(ibuffer); *dirty = ibuffer; memcpy(screen.buffer[ibuffer], &screen.memTiles[tile * BUF_WSIZE], BUF_WSIZE << 1); screen.bufferOffset[ibuffer] = screen.pageOffset[screen.currentPage] + (pageX << 1) + (pageY * PAGE_STRIDE * 16); } return *dirty; } void drawSpriteToBuf(unsigned int *sprite, int pageX, int pageY, int shift, int yStart) { unsigned int *buf, *mask; unsigned int maskval; int y, h, plane; if (pageX < 0 || pageY < 0 || pageX >= PAGE_TILES_W || pageY >= PAGE_TILES_H || shift >= 16 || shift <= -16 || yStart <= -16 || yStart >= 16) { return; } buf = screen.buffer[prepareBuffer(pageX, pageY)]; if (yStart < 0) { sprite = &sprite[-yStart]; h = yStart + 16; } else { buf = &buf[yStart]; h = 16 - yStart; } mask = &sprite[BUF_WSTRIDE * 4]; if (shift < 0) { shift = -shift; for (plane = 0; plane < 4; plane ++) { for (y = 0; y < h; y ++) { maskval = mask[y] << shift; buf[y] = (buf[y] & ~maskval) | ((sprite[y] << shift) & maskval); } sprite += BUF_WSTRIDE; buf += BUF_WSTRIDE; } } else { for (plane = 0; plane < 4; plane ++) { for (y = 0; y < h; y ++) { maskval = mask[y] >> shift; buf[y] = (buf[y] & ~maskval) | ((sprite[y] >> shift) & maskval); } sprite += BUF_WSTRIDE; buf += BUF_WSTRIDE; } } } void drawSprite(unsigned int *sprite, int x, int y) { int pageX = (int)(x - (screen.scrollX & 0xfff0)) >> 4; int pageY = (int)(y - (screen.scrollY & 0xfff0)) >> 4; int pageOffsetX = x & 0x0f; int pageOffsetY = y & 0x0f; drawSpriteToBuf(sprite, pageX, pageY, pageOffsetX, pageOffsetY); drawSpriteToBuf(sprite, pageX + 1, pageY, pageOffsetX - 16, pageOffsetY); drawSpriteToBuf(sprite, pageX, pageY + 1, pageOffsetX, pageOffsetY - 16); drawSpriteToBuf(sprite, pageX + 1, pageY + 1, pageOffsetX - 16, pageOffsetY - 16); } void scroll(int newX, int newY) { newX = min(max(newX, 0), (screen.w << 4) - 320); newY = min(max(newY, 0), (screen.h << 4) - 176); if ((screen.scrollX & 0xfff0) != (newX & 0xfff0) || (screen.scrollY & 0xfff0) != (newY & 0xfff0)) { int mapX, mapY; unsigned char page; for (page = 0; page < 2; page ++) { int mapOffsetOld = (screen.scrollX >> 4) + ((screen.scrollY >> 4) * screen.w); int mapOffsetNew = (newX >> 4) + ((newY >> 4) * screen.w); unsigned char *dirty = screen.dirty[page]; for (mapY = 0; mapY < PAGE_TILES_H; mapY ++) { for (mapX = 0; mapX < PAGE_TILES_W; mapX ++) { if (*dirty != D_NOTHING || screen.map[mapOffsetOld + mapX] != screen.map[mapOffsetNew + mapX]) { *dirty = D_BGTILE; } dirty ++; } mapOffsetNew += screen.w; mapOffsetOld += screen.w; } } } screen.scrollX = newX; screen.scrollY = newY; } void drawScreen() { unsigned int startX = screen.scrollX >> 4; unsigned int startY = screen.scrollY >> 4; unsigned int offsetX = screen.scrollX - (startX << 4); unsigned int offsetY = screen.scrollY - (startY << 4); unsigned int drawOffset = screen.pageOffset[screen.currentPage]; unsigned int scrollOffset = drawOffset + (offsetX >> 3) + (offsetY * PAGE_STRIDE); unsigned char *dirty = screen.dirty[screen.currentPage]; unsigned int x, y, di, plane, bmp; setAllPlanes(); setWriteMode(1); di = 0; for (y = startY; y < startY + PAGE_TILES_H; y ++) { for (x = startX; x < startX + PAGE_TILES_W; x ++) { if (dirty[di++] == D_BGTILE) { blitTile( screen.tilesOffset + (screen.map[x + (y * screen.w)] << 5), drawOffset); } drawOffset += 2; } drawOffset += PAGE_STRIDE * 15; } setWriteMode(0); for(plane = 0; plane < 4; plane ++) { setPlane(plane); for (di = screen.firstBuffer; di != screen.nextBuffer; di = nextBufferIndex(di)) { drawOffset = screen.bufferOffset[di] >> 1; for (y = 0; y < 16; y ++) { bmp = screen.buffer[di][y + (BUF_WSTRIDE * plane)]; WVID[drawOffset] = (bmp << 8) | (bmp >> 8); drawOffset += PAGE_STRIDE >> 1; } } } setAllPlanes(); setDisplayOffset(scrollOffset); setHorizontalPan(screen.scrollX & 0x07); screen.currentPage ^= 1; screen.firstBuffer = screen.nextBuffer; for (di = 0; di < PAGE_TILES_COUNT; di ++) { dirty[di] = isBufIndex(dirty[di]) ? D_BGTILE : D_NOTHING; } } /*** S C R A T C H ***/ #define NUM_TILES 128 #define NUM_SPRITES 64 #define OFF_TILES 0x4840 #define TILE_STRIDE 64 #define SPRITE_STRIDE 80 unsigned int tiles[NUM_TILES * TILE_STRIDE]; unsigned int sprites[NUM_SPRITES * SPRITE_STRIDE]; unsigned char map[10000]; void fillMap() { unsigned int x, y, z; z = 0; for (y = 0; y < 100; y ++) { for (x = 0; x < 100; x ++) { map[x + (y * 100)] = ((x + y + z) >> 2) % 4; } } } void game_init() { FILE *f; TifImageMeta_t meta; setEGAMode(); atexit(vid_cleanup); tile_init(); fillMap(); f = fopen("FOOTER.TIF", "rb"); meta = tifLoadMeta(f); tifLoadEGA(f, meta, 0, 24, 336); fclose(f); f = fopen("TILES.TIF", "rb"); meta = tifLoadMeta(f); tifLoad(f, meta, tiles, NUM_TILES * 16, 16, 4); tifLoadEGA(f, meta, OFF_TILES, NUM_TILES * 16, 16); fclose(f); f = fopen("SPRITE.TIF", "rb"); meta = tifLoadMeta(f); tifLoad(f, meta, sprites, NUM_SPRITES * 16, 16, 5); fclose(f); setSplitScreen(351); loadTiles(OFF_TILES, tiles); loadMap(map, 100, 100); scroll(0, 0); } int main() { int x = -1; int y = -1; int dir = 0; game_init(); kbd_init(); while (!keyPressed(K_ESC)) { if (keyPressed(K_LEFT)) { x -= 3; dir = 2; } if (keyPressed(K_RIGHT)) { x += 3; dir = 0; } if (keyPressed(K_UP)) { y -= 3; dir = 1; } if (keyPressed(K_DOWN)) { y += 3; dir = 3; } scroll(x - 152, y - 90); drawSprite(&sprites[dir * SPRITE_STRIDE], x, y); drawScreen(); } return 0; }