]> 4ch.mooo.com Git - 16.git/blobdiff - 16/keen456/KEEN4-6/CK_DEMO.C
extrcted keen code remake
[16.git] / 16 / keen456 / KEEN4-6 / CK_DEMO.C
diff --git a/16/keen456/KEEN4-6/CK_DEMO.C b/16/keen456/KEEN4-6/CK_DEMO.C
new file mode 100755 (executable)
index 0000000..5004cf3
--- /dev/null
@@ -0,0 +1,2132 @@
+/* Reconstructed Commander Keen 4-6 Source Code\r
+ * Copyright (C) 2021 K1n9_Duk3\r
+ *\r
+ * This file is loosely based on:\r
+ * Keen Dreams Source Code\r
+ * Copyright (C) 2014 Javier M. Chavez\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License along\r
+ * with this program; if not, write to the Free Software Foundation, Inc.,\r
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+ */\r
+\r
+#include "CK_DEF.H"\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                GLOBAL VARIABLES\r
+\r
+=============================================================================\r
+*/\r
+\r
+boolean scorescreenkludge;\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                LOCAL VARIABLES\r
+\r
+=============================================================================\r
+*/\r
+\r
+#if GRMODE == EGAGR\r
+\r
+Uint8 starcolors[17] = STARPALETTE;\r
+Uint16 plaquenum[4] = {IDSOFTPIC, PROGTEAMPIC, ARTISTPIC, DIRECTORPIC};\r
+Uint8 termcolors[17] = INTROPALETTE;\r
+Uint8 termcolors2[17] = SHRINKPALETTE;\r
+\r
+Uint8 ortoend[8] = {0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01};\r
+Uint8 andtoend[8] = {0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE};\r
+\r
+/////////////////////////////////////////////////////////////////////////////\r
+// uninitialized variables:\r
+/////////////////////////////////////////////////////////////////////////////\r
+\r
+typedef struct {\r
+       Uint16 height;\r
+       Uint16 width;\r
+       Uint16 rowofs[200];\r
+} shapehead;\r
+\r
+typedef shapehead _seg * shapeseg;\r
+\r
+// text crawl variables:\r
+memptr linecode;\r
+void far *linestarts[200];\r
+Uint16 sourceline[200];\r
+Uint16 masterlines;\r
+void far *routine;\r
+memptr sourcepic;\r
+memptr bittables;\r
+\r
+// terminator intro variables:\r
+shapeseg commander;\r
+shapeseg keen;\r
+shapeseg both;\r
+memptr scaletable;\r
+memptr cmdrshifts[8];\r
+Sint16 commanderbwide;\r
+Uint16 lastsource;\r
+Uint16 keenstart;\r
+memptr basepl[5];\r
+Uint16 baseplwidth[5];\r
+Uint16 baseplheight[5];\r
+memptr plaqueseg;\r
+Uint16 plaquewidth;\r
+Uint16 plaquewidthwords;\r
+Uint16 plaqueheight;\r
+Uint16 plaqueplane;\r
+Uint16 plaquedelta;\r
+Uint16 *shiftptr;\r
+Uint16 planeon;\r
+Sint16 drawheight;\r
+Uint16 source2;\r
+static Uint16 t_dest;\r
+static Sint16 plaque;\r
+static Sint16 plaquephase;\r
+static Sint16 plaquey;\r
+static Sint16 lastframe;\r
+static Sint16 pageon;\r
+static Sint16 prevbottom[2];\r
+Uint16 pageofs;\r
+Uint16 byteadjust;\r
+\r
+#endif // if GRMODE == EGAGR\r
+\r
+//===========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= CheckLastScan\r
+=\r
+============================\r
+*/\r
+\r
+void CheckLastScan(void)\r
+{\r
+       if (LastScan)\r
+       {\r
+               if (storedemo)\r
+               {\r
+                       playstate = ex_resetgame;\r
+                       restartgame = gd_Normal;\r
+                       IN_ClearKeysDown();\r
+                       NewGame();\r
+               }\r
+#ifndef KEEN6\r
+               else if (LastScan == sc_F1)\r
+               {\r
+                       HelpScreens();\r
+               }\r
+#endif\r
+               else\r
+               {\r
+                       US_ControlPanel();\r
+                       if (restartgame)\r
+                       {\r
+                               playstate = ex_resetgame;\r
+                       }\r
+                       else if (loadedgame)\r
+                       {\r
+                               playstate = ex_loadedgame;\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+#if GRMODE == EGAGR\r
+/*\r
+=============================================================================\r
+\r
+                                                       TERMINATOR INTRO\r
+\r
+=============================================================================\r
+*/\r
+\r
+/*\r
+============================\r
+=\r
+= LoadPlaque\r
+=\r
+============================\r
+*/\r
+\r
+void LoadPlaque(Sint16 index)\r
+{\r
+       Sint16 LocatePlaque(Sint16 elapsed);\r
+\r
+       Uint16 chunk, picnum, width, height, planesize, i;\r
+       Uint8 far *source;\r
+       Uint16 far *dest;\r
+\r
+       //\r
+       // cache the pic and get pic size\r
+       //\r
+       chunk = plaquenum[index];\r
+       CA_CacheGrChunk(chunk);\r
+       picnum = chunk - STARTPICS;\r
+       baseplwidth[index] = width = pictable[picnum].width;\r
+       baseplheight[index] = height = pictable[picnum].height;\r
+       planesize = width * height * 2;\r
+\r
+       //\r
+       // allocate buffer and convert pic into to our format\r
+       // (convert bytes to word indices for faster shift-drawing)\r
+       //\r
+       MM_GetPtr(&basepl[index], planesize*2); // 2 planes\r
+       source = grsegs[chunk];\r
+       dest = basepl[index];\r
+       for (i=0; i<planesize; i++)\r
+       {\r
+               *dest++ = *source++ << 1;\r
+       }\r
+\r
+       //\r
+       // pic in original format is no longer needed\r
+       //\r
+       MM_FreePtr(&grsegs[chunk]);\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= DrawPlaque\r
+=\r
+============================\r
+*/\r
+\r
+void DrawPlaque(Sint16 elapsed, Uint16 x)\r
+{\r
+       Uint16 shift, xb;\r
+       Sint16 y, bottom, oldbottom;\r
+       Uint16 eraseheight, skip, screenoff;\r
+\r
+       shift = x & 7;\r
+       xb = (pageofs + (x / 8)) + (20 - (plaquewidth >> 1));\r
+\r
+       EGAMAPMASK(12); // write to "red" and "intensity" plane (for erasing old pic)\r
+\r
+       //\r
+       // update position (and pic number)\r
+       //\r
+       y = LocatePlaque(elapsed);\r
+\r
+       //\r
+       // erase leftovers of the previous frame\r
+       //\r
+       bottom = y + plaqueheight;\r
+       if (bottom < 0)\r
+               bottom = 0;\r
+       oldbottom = prevbottom[pageon];\r
+       if (bottom < 200 && oldbottom > bottom)\r
+       {\r
+               eraseheight = oldbottom - bottom;\r
+               screenoff = xb + ylookup[bottom];\r
+               asm {\r
+                       mov     es, screenseg;\r
+                       mov     bx, linewidth;\r
+                       sub     bx, plaquewidthwords;\r
+                       sub     bx, plaquewidthwords;\r
+                       mov     di, screenoff;\r
+                       mov     dx, eraseheight;\r
+                       mov     si, plaquewidthwords;\r
+                       xor     ax, ax;\r
+               }\r
+eraseloop:\r
+               asm {\r
+                       mov     cx, si;\r
+                       rep stosw;\r
+                       add     di, bx;\r
+                       dec     dx;\r
+                       jnz     eraseloop;\r
+               }\r
+       }\r
+       if (bottom > 200)\r
+               bottom = 200;\r
+       prevbottom[pageon] = bottom;\r
+\r
+       //\r
+       // draw the (new) pic at the new position\r
+       //\r
+       drawheight = plaqueheight;\r
+       skip = 0;\r
+       if (y < 0)\r
+       {\r
+               skip = -y * (plaquewidth << 1);\r
+               drawheight += y;\r
+               y = 0;\r
+       }\r
+       else if (y + plaqueheight > 200)\r
+       {\r
+               drawheight = 200 - y;\r
+       }\r
+       source2 = skip + plaqueplane;\r
+       if (drawheight > 0)\r
+       {\r
+               shiftptr = shifttabletable[shift];\r
+               t_dest = xb + ylookup[y];\r
+               asm {\r
+                       mov     bx, skip;\r
+                       push    bp;\r
+                       mov     bp, shiftptr;\r
+                       mov     es, screenseg;\r
+                       mov     ds, plaqueseg;\r
+                       mov     ah, 4;\r
+                       mov     BYTE PTR ss:planeon, ah;\r
+               }\r
+planeloop:\r
+               asm {\r
+                       mov     dx, SC_INDEX;\r
+                       mov     al, SC_MAPMASK;\r
+                       out     dx, ax;\r
+                       mov     dx, ss:drawheight;\r
+                       mov     di, ss:t_dest;\r
+               }\r
+yloop:\r
+               asm {\r
+                       mov     cx, ss:plaquewidth;\r
+                       xor     al, al;\r
+               }\r
+xloop:\r
+               asm {\r
+                       mov     si, [bx];\r
+                       add     bx, 2;\r
+                       xor     ah, ah;\r
+                       or      ax, [bp+si];\r
+                       stosb;\r
+                       mov     al, ah;\r
+                       loop    xloop;\r
+                       stosb;\r
+                       mov     WORD PTR es:[di], 0;\r
+                       add     di, ss:plaquedelta;\r
+                       dec     dx;\r
+                       jnz     yloop;\r
+                       mov     bx, ss:source2;\r
+                       mov     ah, BYTE PTR ss:planeon;\r
+                       shl     ah, 1;\r
+                       mov     BYTE PTR ss:planeon, ah;\r
+                       cmp     ah, 16;\r
+                       jnz     planeloop;\r
+                       pop     bp;\r
+                       mov     ax, ss;\r
+                       mov     ds, ax;\r
+               }\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= LocatePlaque\r
+=\r
+============================\r
+*/\r
+\r
+Sint16 LocatePlaque(Sint16 elapsed)\r
+{\r
+       switch (plaquephase)\r
+       {\r
+       case -1:\r
+               //\r
+               // pic starts to appear\r
+               //\r
+               plaqueseg = basepl[plaque];\r
+               plaquewidth = baseplwidth[plaque];\r
+               plaquewidthwords = (plaquewidth + 3) >> 1;\r
+               plaqueheight = baseplheight[plaque];\r
+               plaquedelta = linewidth - (plaquewidth + 1);\r
+               plaqueplane = (plaquewidth * plaqueheight) << 1;\r
+               plaquephase++;\r
+               lastframe = elapsed;\r
+               plaquey = 240;\r
+               // no break or return here!\r
+       case 0:\r
+               //\r
+               // pic is moving from the bottom to the center of the screen\r
+               //\r
+               plaquey -= (elapsed - lastframe) << 1;\r
+               if (plaquey < 100)\r
+               {\r
+                       plaquey = 100;\r
+                       plaquephase++;\r
+               }\r
+               lastframe = elapsed;\r
+               return plaquey - (plaqueheight >> 1);\r
+\r
+       case 1:\r
+               //\r
+               // pic is staying at the center position\r
+               //\r
+               if (elapsed - lastframe > 200)\r
+               {\r
+                       plaquephase++;\r
+                       lastframe = elapsed;\r
+               }\r
+               return 100 - (plaqueheight >> 1);\r
+\r
+       case 2:\r
+               //\r
+               // pic is moving up from the center to the top of the screen\r
+               //\r
+               plaquey -= (elapsed - lastframe) << 1;\r
+               if (plaquey < -40)\r
+               {\r
+                       plaquey = -40;\r
+                       if (++plaque < 4)\r
+                       {\r
+                               plaquephase = -1;\r
+                       }\r
+                       else\r
+                       {\r
+                               plaquephase = 3;\r
+                       }\r
+               }\r
+               lastframe = elapsed;\r
+               return plaquey - (plaqueheight >> 1);\r
+       }\r
+\r
+       return -40;\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= SlideLetters\r
+=\r
+============================\r
+*/\r
+\r
+void SlideLetters(void)\r
+{\r
+       Sint16 x, cPosX, screenxb;\r
+       Uint16 elapsed, totaltics, dstofs;\r
+       Sint16 cStart, cEnd, cTotalMove;\r
+       Uint16 shift, srcseg, srcofs;\r
+       Sint16 clearleft, copywidth, clearright;\r
+       Uint16 srcdif, dstdif;\r
+       Sint32 now;\r
+\r
+       //\r
+       // set up characteristics of the animation\r
+       //\r
+       EGAWRITEMODE(0);\r
+       EGAREADMAP(0);  // useless...\r
+\r
+       keenstart = keen->width + 200;\r
+       EGAREADMAP(1);  // also useless ... I think...\r
+\r
+       cEnd = 120 - commander->width;\r
+       cStart = 320;\r
+       cTotalMove = cEnd - cStart;\r
+       totaltics = abs(cTotalMove);\r
+\r
+       pageofs = pageon = 0;\r
+       lasttimecount = TimeCount;\r
+       while (TimeCount == lasttimecount);\r
+       lasttimecount = TimeCount;\r
+\r
+       for (elapsed=0; elapsed <= totaltics; elapsed += tics)\r
+       {\r
+               //\r
+               // draw the credits pic\r
+               //\r
+               x = ((Sint32)keenstart * (Sint32)(totaltics-elapsed)) / (Sint32)totaltics;\r
+               DrawPlaque(elapsed, x);\r
+\r
+               //\r
+               // get ready to draw draw the "COMMANDER" pic\r
+               //\r
+               cPosX = cStart + ((Sint32)cTotalMove * (Sint32)elapsed) / (Sint32)totaltics;\r
+               cPosX += x & 7;\r
+               screenxb = (cPosX + 0x800) / 8 + -0x100;\r
+               shift = (cPosX + 0x800) & 7;\r
+               srcseg = FP_SEG(cmdrshifts[shift]);\r
+               srcofs = 0;\r
+               dstofs = pageofs + x / 8;\r
+               if (screenxb > 0)\r
+               {\r
+                       clearleft = (screenxb + 1) / 2;\r
+                       if (screenxb & 1)\r
+                               dstofs--;\r
+                       copywidth = 21 - clearleft;\r
+                       clearright = 0;\r
+               }\r
+               else if (-commanderbwide + 40 < screenxb)\r
+               {\r
+                       clearleft = 0;\r
+                       copywidth = 21;\r
+                       clearright = 0;\r
+                       srcofs -= screenxb;\r
+               }\r
+               else\r
+               {\r
+                       clearleft = 0;\r
+                       copywidth = (commanderbwide + screenxb) / 2;\r
+                       clearright = 21 - copywidth;\r
+                       srcofs -= screenxb;\r
+               }\r
+               srcdif = commanderbwide - copywidth*2;\r
+               dstdif = 248 - (clearleft + copywidth + clearright)*2;\r
+\r
+               //\r
+               // draw "COMMANDER" pic\r
+               //\r
+               EGAMAPMASK(2);\r
+\r
+               asm {\r
+                       mov     di, dstofs;\r
+                       mov     es, screenseg;\r
+                       mov     si, srcofs;\r
+                       mov     lastsource, si;\r
+                       mov     ds, srcseg;\r
+                       mov     dx, 200;\r
+               }\r
+yloop:\r
+               asm {\r
+                       xor     ax, ax;\r
+                       mov     cx, clearleft;\r
+                       rep stosw;\r
+                       mov     cx, copywidth;\r
+                       rep movsw;\r
+                       xor     ax, ax;\r
+                       mov     cx, clearright;\r
+                       rep stosw;\r
+                       test    dx, 1;\r
+                       jnz     oddline;\r
+                       mov     si, ss:lastsource;\r
+                       jmp     nextline;\r
+               }\r
+oddline:\r
+               asm {\r
+                       add     si, srcdif;\r
+                       mov     ss:lastsource, si;\r
+               }\r
+nextline:\r
+               asm {\r
+                       add     di, dstdif;\r
+                       dec     dx;\r
+                       jnz     yloop;\r
+                       mov     ax, ss;\r
+                       mov     ds, ax;\r
+               }\r
+\r
+               //\r
+               // page flip\r
+               //\r
+               VW_SetScreen(pageofs + x / 8, x & 7);\r
+               pageon ^= 1;\r
+               if (pageon)\r
+               {\r
+                       pageofs = 124;\r
+               }\r
+               else\r
+               {\r
+                       pageofs = 0;\r
+               }\r
+\r
+               //\r
+               // handle timing\r
+               //\r
+               do\r
+               {\r
+                       now = TimeCount;\r
+                       tics = now - lasttimecount;\r
+               } while (tics < 2);\r
+               lasttimecount = now;\r
+\r
+               //\r
+               // handle input\r
+               //\r
+               if (IN_IsUserInput() && LastScan != sc_F1)\r
+               {\r
+                       LastScan = sc_Space;\r
+               }\r
+               if (LastScan)\r
+                       return;\r
+       }\r
+\r
+       byteadjust = x / 8;\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= DrawScan\r
+=\r
+============================\r
+*/\r
+\r
+void DrawScan(Sint16 far *source, Uint8 far *dest)\r
+{\r
+       register Uint16 x;\r
+       register Sint16 w;\r
+       register Uint16 val;\r
+       register Uint16 i;\r
+\r
+       val = x = 0;\r
+       for (;;)\r
+       {\r
+               //\r
+               // first part: puts black pixels (<width> pixels wide)\r
+               //\r
+               w = *source++;\r
+               if (w == -1)\r
+               {\r
+                       *dest++ = val;\r
+                       *dest = 0;\r
+                       return;\r
+               }\r
+\r
+               x += w;\r
+               if (x > 7)\r
+               {\r
+                       *dest++ = val;\r
+                       val = 0;\r
+                       i = (x / 8) - 1;\r
+                       while (i--)\r
+                       {\r
+                               *dest++ = 0;\r
+                       }\r
+                       x &= 7;\r
+               }\r
+\r
+               //\r
+               // second part: puts white pixels (<width> pixels wide)\r
+               //\r
+               w = *source++;\r
+               if (w == -1)\r
+               {\r
+                       *dest++ = val;\r
+                       *dest = 0;\r
+                       return;\r
+               }\r
+\r
+               val |= ortoend[x];\r
+               x += w;\r
+               if (x > 7)\r
+               {\r
+                       *dest++ = val;\r
+                       val = 0xFF;\r
+                       i = (x / 8) - 1;\r
+                       while (i--)\r
+                       {\r
+                               *dest++ = 0xFF;\r
+                       }\r
+                       x &= 7;\r
+               }\r
+               val &= andtoend[x];\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= BuildScaleShape\r
+=\r
+============================\r
+*/\r
+\r
+void BuildScaleShape(void)\r
+{\r
+       Sint16 px, w;\r
+       Sint16 far *source;\r
+       Sint16 far *dest;\r
+       Sint16 y;\r
+\r
+       MM_GetPtr((memptr*)&both, 30000);\r
+       dest = MK_FP(FP_SEG(both), sizeof(shapehead));\r
+\r
+       for (y=0; y<200; y++)\r
+       {\r
+               both->rowofs[y] = FP_OFF(dest);\r
+               px = 0;\r
+\r
+               EGAREADMAP(1);  // this is pretty useless, we're not reading from EGA memory here\r
+\r
+               source = (Sint16 far *)((byte _seg *)commander + commander->rowofs[y]);\r
+               w = *source++;\r
+               do\r
+               {\r
+                       *dest++ = px;\r
+                       px = px + w;\r
+                       w = *source++;\r
+               } while (w != -1);\r
+\r
+               //\r
+               // insert an 80 pixel gap between "COMMANDER" and "KEEN"\r
+               //\r
+               // This assumes that the rightmost column(s) of the "COMMANDER"\r
+               // shape are black. Otherwise the gap would be filled with\r
+               // white pixels and the "KEEN" image would use inverted colors\r
+               // as a result.\r
+               //\r
+               px += 80;\r
+\r
+               EGAREADMAP(0);  // this is pretty useless, we're not reading from EGA memory here\r
+\r
+               source = (Sint16 far *)((byte _seg *)keen + keen->rowofs[y]);\r
+               source++;       // kludgy bit, causes errors when left egde of "KEEN" is no rectangle\r
+               w = *source++;\r
+               do\r
+               {\r
+                       *dest++ = px;\r
+                       px = px + w;\r
+                       w = *source++;\r
+               } while (w != -1);\r
+\r
+               *dest++ = px;   // put last value\r
+               *dest++ = -1;   // put end-of-line\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= ScalePointScan\r
+=\r
+============================\r
+*/\r
+\r
+void ScalePointScan(Sint16 far *rowptr, Sint16 y, Sint16 toleft, Sint16 far *scaletable)\r
+{\r
+       Uint8 far *dest;\r
+       Sint16 left, endx;\r
+       Uint16 w, val, x, right;\r
+       register Sint16 px, sx;\r
+\r
+       val = x = 0;\r
+       endx = 320 - toleft;\r
+       dest = MK_FP(0xA000, pageofs + byteadjust + ylookup[y]);\r
+\r
+       if (toleft < 0)\r
+       {\r
+               left = -toleft;\r
+               val = 0;\r
+               x = 0;\r
+\r
+               for (;;)\r
+               {\r
+                       px = *rowptr++;\r
+                       sx = scaletable[px];\r
+                       if (sx > left)\r
+                               goto drawwhite;\r
+\r
+                       px = *rowptr++;\r
+                       sx = scaletable[px];\r
+                       if (sx > left)\r
+                               goto drawblack;\r
+               }\r
+       }\r
+\r
+       //\r
+       // regular\r
+       //\r
+       val = 0;\r
+       x = toleft & 7;\r
+       dest += (toleft >> 3);\r
+       left = 0;\r
+       rowptr++;       // the first value is always 0, we need the next value\r
+drawloop:\r
+       px = *rowptr++;\r
+       sx = scaletable[px];\r
+\r
+       //\r
+       // draw/add black pixels\r
+       //\r
+drawblack:\r
+       w = sx - left;\r
+       left = sx;\r
+       x += w;\r
+       if (x > 7)\r
+       {\r
+               asm {\r
+                       les     di, dest;\r
+                       mov     al, BYTE PTR val;\r
+                       stosb;\r
+                       mov     cx, x;\r
+                       shr     cx, 1;\r
+                       shr     cx, 1;\r
+                       shr     cx, 1;\r
+                       dec     cx;\r
+                       xor     al, al;\r
+                       mov     BYTE PTR val, al;\r
+                       rep stosb;\r
+                       and     x, 7;\r
+                       mov     WORD PTR dest, di;\r
+               }\r
+       }\r
+\r
+       //\r
+       // stop if the right side of the screen is reached\r
+       //\r
+       if (sx > endx)\r
+               return;\r
+\r
+       //\r
+       // stop if the end of the image row is reached\r
+       // \r
+       // This is only checked after drawing the black part, so the\r
+       // combined shape must not end with white pixels on the right.\r
+       // That means the rightmost column(s) of the "KEEN" shape must\r
+       // always be black.\r
+       //\r
+       px = *rowptr++;\r
+       if (px == -1)\r
+               goto clearright;\r
+\r
+       sx = scaletable[px];\r
+\r
+       //\r
+       // draw/add white pixels\r
+       //\r
+drawwhite:\r
+       w = sx - left;\r
+       left = sx;\r
+       val |= ortoend[x];\r
+       x += w;\r
+       if (x > 7)\r
+       {\r
+               asm {\r
+                       les     di, dest;\r
+                       mov     al, BYTE PTR val;\r
+                       stosb;\r
+                       mov     cx, x;\r
+                       shr     cx, 1;\r
+                       shr     cx, 1;\r
+                       shr     cx, 1;\r
+                       dec     cx;\r
+                       mov     al, 255;\r
+                       mov     BYTE PTR val, al;\r
+                       rep stosb;\r
+                       and     x, 7;\r
+                       mov     WORD PTR dest, di;\r
+               }\r
+       }\r
+\r
+       //\r
+       // stop if the right side of the screen is reached\r
+       //\r
+       if (sx > endx)\r
+               return;\r
+\r
+       val &= andtoend[x];\r
+       goto drawloop;\r
+\r
+       //\r
+       // clear the right side of the screen\r
+       //\r
+clearright:\r
+       w = 320 - left;\r
+       x += w;\r
+       if (x > 7)\r
+       {\r
+               *dest++ = val;\r
+               val = 0;\r
+               right = x / 8 - 1;\r
+               while (right--)\r
+               {\r
+                       *dest++ = 0;\r
+               }\r
+               x &= 7;\r
+               return;\r
+       }\r
+       return;\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= ScaleDown\r
+=\r
+============================\r
+*/\r
+\r
+void ScaleDown(void)\r
+{\r
+       Uint16 i;\r
+       Uint16 toleft, ticselapsed, ticstotal, scale, endscale, rownum, rowinc;\r
+       Sint32 now;\r
+       Sint16 far *rowptr;\r
+       Uint16 scaleheight, top, bottom, lastbottom[2];\r
+       Sint32 leftorigin;\r
+\r
+       //\r
+       // set our new palette\r
+       //\r
+       SetPalette(termcolors2);\r
+\r
+       EGAREADMAP(1);  // this is pretty useless, we're not reading from EGA memory here\r
+\r
+       leftorigin = 120l - commander->width;\r
+       BuildScaleShape();\r
+       MM_GetPtr(&scaletable, 2500*sizeof(Uint16));\r
+\r
+       scale = 0x100;          // 100%\r
+       endscale = 0x21;        // 13% (scale from 200px to 26px)\r
+       endscale = 0x21;        // redundant\r
+       lastbottom[0] = lastbottom[1] = 200;\r
+       ticselapsed = 1;\r
+       ticstotal = 30; // time for the whole shrinking animation\r
+\r
+       while (ticselapsed <= ticstotal)\r
+       {\r
+               //\r
+               // get current scaling\r
+               //\r
+               if (ticselapsed == ticstotal)\r
+               {\r
+                       scale = endscale;\r
+                       toleft = 0;\r
+                       top = 4;\r
+               }\r
+               else\r
+               {\r
+                       scale = 0x100 - ((0x100-endscale) * ticselapsed) / ticstotal;\r
+                       toleft = (leftorigin * (ticstotal - ticselapsed)) / ticstotal;\r
+                       top = (ticselapsed * 4) / ticstotal;\r
+               }\r
+\r
+               //\r
+               // build scale table:           scaletable[i] = (i*scale) / 0x100;\r
+               //\r
+               asm {\r
+                       xor     ax, ax;\r
+                       xor     dx, dx;\r
+                       mov     cx, 2500;\r
+                       mov     bx, scale;\r
+                       mov     es, scaletable;\r
+                       xor     di, di;\r
+               }\r
+l1:\r
+               asm {\r
+                       mov     es:[di], ah;\r
+                       inc     di;\r
+                       mov     es:[di], dl;\r
+                       inc     di;\r
+                       add     ax, bx;\r
+                       adc     dx, 0;\r
+                       loop    l1;\r
+               }\r
+\r
+               //\r
+               // wait... didn't we already do this?\r
+               //\r
+               if (ticselapsed == ticstotal)\r
+               {\r
+                       toleft = 0;\r
+               }\r
+               else\r
+               {\r
+                       toleft = (leftorigin * (ticstotal - ticselapsed)) / ticstotal;\r
+               }\r
+\r
+               //\r
+               // prepare scaled drawing process\r
+               //\r
+               scaleheight = ((Sint16 _seg *)scaletable)[200];\r
+               rownum = 0;\r
+               rowinc = 0x10000l / scale;\r
+               bufferofs = pageofs + byteadjust;\r
+\r
+               //\r
+               // erase stuff at the top\r
+               //\r
+               if (top > 0)\r
+               {\r
+                       VW_Bar(0, 0, 320, top, BLACK);\r
+               }\r
+\r
+               //\r
+               // draw the scaled shape\r
+               //\r
+               EGAWRITEMODE(0);\r
+               EGAMAPMASK(15);\r
+\r
+               for (i=0; i<scaleheight; i++)\r
+               {\r
+                       rowptr = (Sint16 far *)((byte _seg *)both + both->rowofs[rownum >> 8]);\r
+                       ScalePointScan(rowptr, i+top, toleft, scaletable);\r
+\r
+                       rownum += rowinc;\r
+               }\r
+\r
+               //\r
+               // erase leftovers at the bottom of the screen\r
+               //\r
+               bufferofs = pageofs + byteadjust;\r
+               bottom = scaleheight + top;\r
+               if (lastbottom[pageon] > bottom)\r
+               {\r
+                       VW_Bar(0, bottom, 320, lastbottom[pageon] - bottom, BLACK);\r
+                       lastbottom[pageon] = bottom;\r
+               }\r
+\r
+               //\r
+               // page flip\r
+               //\r
+               VW_SetScreen(pageofs+byteadjust, 0);\r
+               pageon ^= 1;\r
+               if (pageon)\r
+               {\r
+                       pageofs = 124;\r
+               }\r
+               else\r
+               {\r
+                       pageofs = 0;\r
+               }\r
+\r
+               //\r
+               // handle timing\r
+               //\r
+               now = TimeCount;\r
+               tics = now - lasttimecount;\r
+               lasttimecount = now;\r
+               if (tics > 8)\r
+                       tics = 8;       // don't skip too many frames on slow systems\r
+\r
+               if (ticselapsed == ticstotal)\r
+                       break;\r
+\r
+               ticselapsed += tics;\r
+               if (ticselapsed > ticstotal)\r
+                       ticselapsed = ticstotal;\r
+\r
+               //\r
+               // handle input\r
+               //\r
+               if (IN_IsUserInput() && LastScan != sc_F1)\r
+               {\r
+                       LastScan = sc_Space;\r
+               }\r
+               if (LastScan)\r
+                       return; // BUG: buffers aren't freed!\r
+       }\r
+\r
+       //\r
+       // free the buffers\r
+       //\r
+       MM_FreePtr(&scaletable);\r
+       MM_FreePtr((memptr*)&both);\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= FinishPage\r
+=\r
+============================\r
+*/\r
+\r
+void FinishPage(void)\r
+{\r
+       Sint16 swap, temp, i, n, x, y;\r
+       Uint16 ofs;\r
+       Sint16 top, bottom, delta;\r
+       Uint8 bitmask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};\r
+       Sint16 xtable[320], ytable[200];\r
+\r
+       //\r
+       // build lookup tables\r
+       //\r
+       for (i=0; i<320; i++)\r
+       {\r
+               xtable[i] = i;\r
+       }\r
+       for (i=0; i<320; i++)\r
+       {\r
+               swap = random(320);\r
+               temp = xtable[swap];\r
+               xtable[swap] = xtable[i];\r
+               xtable[i] = temp;\r
+       }\r
+       for (i=0; i<200; i++)\r
+       {\r
+               ytable[i] = xtable[i];\r
+       }\r
+\r
+       //\r
+       // set up display\r
+       //\r
+       VW_SetDefaultColors();\r
+       if (pageon)\r
+       {\r
+               bufferofs = byteadjust + 124;\r
+               displayofs = byteadjust;\r
+       }\r
+       else\r
+       {\r
+               bufferofs = byteadjust;\r
+               displayofs = byteadjust + 124;\r
+       }\r
+       VW_SetScreen(displayofs, 0);\r
+\r
+       //\r
+       // draw title pic to the non-displayed buffer\r
+       //\r
+       VW_DrawPic(0, 0, TITLEPICPIC);\r
+\r
+       //\r
+       // copy "random" pixels from the non-displayed area\r
+       // into the displayed area to create the "fizzle" effect\r
+       //\r
+       delta = displayofs - bufferofs;\r
+\r
+       //\r
+       // set ES register for the pixel copying code in the loops\r
+       //\r
+       // This is faster than setting the ES register in the loops,\r
+       // but you need to make sure nothing in the loops overwrites\r
+       // the ES register, otherwise the code won't work correctly.\r
+       //\r
+       asm     mov     es, screenseg;\r
+\r
+       for (i = 0; i< 360; i++)\r
+       {\r
+               top = i - 160;\r
+               if (top < 0)\r
+                       top = 0;\r
+\r
+               bottom = i;\r
+               if (bottom >= 200)\r
+                       bottom = 199;\r
+\r
+               for (y = top; y <= bottom; y++)\r
+               {\r
+                       ofs = bufferofs + ylookup[y];\r
+                       for (n=0; n<2; n++)\r
+                       {\r
+                               x = xtable[ytable[y]];\r
+                               if (++ytable[y] == 320)\r
+                               {\r
+                                       ytable[y] = 0;\r
+                               }\r
+\r
+                               //\r
+                               // set bitmask for our x value\r
+                               //\r
+                               asm     mov     cx, x;\r
+                               asm     mov     si, cx;\r
+                               asm     and     si, 7;\r
+                               asm     cli;\r
+                               asm     mov     dx, GC_INDEX;\r
+                               asm     mov     al, GC_BITMASK;\r
+                               asm     mov     ah, BYTE PTR bitmask[si];\r
+                               asm     out     dx, ax;\r
+                               asm     sti;\r
+\r
+                               //\r
+                               // set up source and dest index registers\r
+                               //\r
+                               asm     mov     si, ofs;\r
+                               asm     shr     cx, 1;\r
+                               asm     shr     cx, 1;\r
+                               asm     shr     cx, 1;\r
+                               asm     add     si, cx;\r
+                               asm     mov     di, si;\r
+                               asm     add     di, delta;\r
+\r
+                               //\r
+                               // copy the pixel data (all 4 planes)\r
+                               //\r
+                               // "blue" plane:\r
+                               asm     mov     dx, SC_INDEX;\r
+                               asm     mov     ax, SC_MAPMASK + 1*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     dx, GC_INDEX;\r
+                               asm     mov     ax, GC_READMAP + 0*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     bl, es:[si];\r
+                               asm     xchg    bl, es:[di];\r
+                               // "green" plane:\r
+                               asm     mov     dx, SC_INDEX;\r
+                               asm     mov     ax, SC_MAPMASK + 2*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     dx, GC_INDEX;\r
+                               asm     mov     ax, GC_READMAP + 1*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     bl, es:[si];\r
+                               asm     xchg    bl, es:[di];\r
+                               // "red" plane:\r
+                               asm     mov     dx, SC_INDEX;\r
+                               asm     mov     ax, SC_MAPMASK + 4*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     dx, GC_INDEX;\r
+                               asm     mov     ax, GC_READMAP + 2*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     bl, es:[si];\r
+                               asm     xchg    bl, es:[di];\r
+                               // "intensity" plane:\r
+                               asm     mov     dx, SC_INDEX;\r
+                               asm     mov     ax, SC_MAPMASK + 8*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     dx, GC_INDEX;\r
+                               asm     mov     ax, GC_READMAP + 3*256;\r
+                               asm     out     dx, ax;\r
+                               asm     mov     bl, es:[si];\r
+                               asm     xchg    bl, es:[di];\r
+                       }\r
+               }\r
+\r
+               VW_WaitVBL(1);  // so the fizzle animation won't go super fast\r
+\r
+               if (IN_IsUserInput() && LastScan != sc_F1)\r
+               {\r
+                       LastScan = sc_Space;\r
+               }\r
+               if (LastScan)\r
+               {\r
+                       EGABITMASK(0xFF);\r
+                       EGAMAPMASK(15);\r
+                       return;\r
+               }\r
+       }\r
+\r
+       //\r
+       // clean up EGA registers\r
+       //\r
+       EGABITMASK(0xFF);\r
+       EGAMAPMASK(15);\r
+\r
+       //\r
+       // pause for 6 seconds\r
+       //\r
+       IN_UserInput(6 * TickBase, false);\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= Terminator\r
+=\r
+============================\r
+*/\r
+\r
+void Terminator(void)\r
+{\r
+       Uint16 i, shift, bufsize;\r
+       Sint16 far *source;\r
+       Uint8 far *dest;\r
+       Uint16 srcseg, destseg;\r
+       boolean pagefinished;\r
+       Uint16 rowofs[200];\r
+\r
+       pagefinished = false;\r
+       CA_SetAllPurge();\r
+       SetPaletteEx(colors[0]);        // all black\r
+       VW_ClearVideo(BLACK);\r
+       VW_SetLineWidth(248);   // 1984 pixels total, we're using 992 per "page"\r
+\r
+       CA_CacheGrChunk(TITLEPICPIC);\r
+       CA_CacheGrChunk(BIGCOMMANDER);\r
+       CA_CacheGrChunk(BIGKEEN);\r
+       keen = grsegs[BIGKEEN];\r
+       commander = grsegs[BIGCOMMANDER];\r
+\r
+       EGAMAPMASK(1);\r
+\r
+       keenstart = keen->width + 200;\r
+       VW_SetScreen((keenstart/8)+1, 0);\r
+\r
+       //\r
+       // draw the "KEEN" pic (to first "page")\r
+       //\r
+       for (i=0; i<200; i++)\r
+       {\r
+               source = (Sint16 far *)((byte _seg *)keen + keen->rowofs[i]);\r
+               dest = MK_FP(0xA000, ylookup[i]);\r
+               dest += 25;     // 25 bytes -> 200 pixels\r
+               DrawScan(source, dest);\r
+       }\r
+       //\r
+       // copy pic from first "page" to second "page"\r
+       //\r
+       VW_ScreenToScreen(0, 124, 109, 200);\r
+\r
+       //\r
+       // create pre-shifted image buffers for the "COMMANDER" pic\r
+       // (only 100 pixels high instead of 200 pixels to save memory)\r
+       //\r
+       commanderbwide = (commander->width + 7) / 8;\r
+       commanderbwide = (commanderbwide + 3) & ~1;\r
+       bufsize = commanderbwide * 100; // half height\r
+       for (shift = 0; shift < 8; shift++)\r
+       {\r
+               MM_GetPtr(&cmdrshifts[shift], bufsize);\r
+       }\r
+\r
+       //\r
+       // re-assign shape pointers (memory manager might have moved the buffers)\r
+       //\r
+       keen = grsegs[BIGKEEN];\r
+       commander = grsegs[BIGCOMMANDER];\r
+\r
+       //\r
+       // draw the first (unshifted) version of the "COMMANDER" pic to the buffer\r
+       //\r
+       for (i=0; i<100; i++)\r
+       {\r
+               rowofs[i*2] = rowofs[i*2+1] = i * commanderbwide;\r
+               source = (Sint16 far *)((byte _seg *)commander + commander->rowofs[i*2]);\r
+               dest = (Uint8 _seg *)cmdrshifts[0] + rowofs[i*2];\r
+               DrawScan(source, dest);\r
+       }\r
+\r
+       //\r
+       // create the shifted versions of the "COMMANDER" pic\r
+       //\r
+       for (shift = 1; shift < 8; shift++)\r
+       {\r
+               srcseg = FP_SEG(cmdrshifts[shift-1]);\r
+               destseg = FP_SEG(cmdrshifts[shift]);\r
+               asm {\r
+                       mov     ds, srcseg;\r
+                       mov     es, destseg;\r
+                       mov     cx, bufsize;\r
+                       clc;\r
+                       xor     si, si;\r
+                       xor     di, di;\r
+               }\r
+l1:\r
+               asm {\r
+                       lodsb;\r
+                       rcr     al, 1;\r
+                       stosb;\r
+                       loop    l1;\r
+                       mov     ax, ss;\r
+                       mov     ds, ax;\r
+               }\r
+       }\r
+\r
+       //\r
+       // prepare (and set) the palettes\r
+       //\r
+       termcolors[16] = termcolors2[16] = termcolors[16] = bordercolor;\r
+       SetPalette(termcolors);\r
+\r
+       //\r
+       // cache the credits pics (they are converted into a special\r
+       // format to make shifted drawing easier during the animation)\r
+       //\r
+       for (i=0; i<4; i++)\r
+       {\r
+               LoadPlaque(i);\r
+       }\r
+\r
+       //\r
+       // play the animation\r
+       //\r
+       plaque = lastframe = 0;\r
+       plaquephase = -1;\r
+       SlideLetters();\r
+\r
+       //\r
+       // free some of the buffers\r
+       // (shrink animation needs additional memory)\r
+       //\r
+       for (i=0; i<4; i++)\r
+       {\r
+               MM_FreePtr(&basepl[i]);\r
+       }\r
+       for (shift=0; shift<8; shift++)\r
+       {\r
+               MM_FreePtr(&cmdrshifts[shift]);\r
+       }\r
+\r
+       //\r
+       // do the shrinking and fizzle fade animations\r
+       // (if intro wasn't aborted)\r
+       //\r
+       if (!LastScan)\r
+       {\r
+               ScaleDown();\r
+       }\r
+\r
+       if (!LastScan)\r
+       {\r
+               FinishPage();\r
+               pagefinished = true;\r
+       }\r
+\r
+       //\r
+       // free the remaining buffers\r
+       //\r
+       MM_SetPurge(&grsegs[BIGCOMMANDER], 3);\r
+       MM_SetPurge(&grsegs[BIGKEEN], 3);\r
+\r
+       //\r
+       // switch back to default graphics settings\r
+       //\r
+       VW_ClearVideo(BLACK);\r
+       VW_SetLineWidth(SCREENWIDTH);\r
+       VW_SetDefaultColors();\r
+       RF_FixOfs();\r
+       CA_ClearMarks();\r
+\r
+       //\r
+       // handle input and main menu stuff\r
+       //\r
+       if (LastScan == sc_None)\r
+       {\r
+               return;\r
+       }\r
+#ifndef KEEN6\r
+       if (LastScan == sc_F1)\r
+       {\r
+               HelpScreens();\r
+               return;\r
+       }\r
+#endif\r
+       if (!pagefinished)\r
+       {\r
+               RF_FixOfs();    //redundant\r
+               CA_CacheGrChunk(TITLEPICPIC);\r
+               VW_DrawPic(0, 0, TITLEPICPIC);\r
+               VW_SetScreen(bufferofs, 0);\r
+               IN_Ack();\r
+               CA_ClearMarks();\r
+               if (storedemo)\r
+               {\r
+                       playstate = ex_resetgame;\r
+                       restartgame = gd_Normal;\r
+                       IN_ClearKeysDown();\r
+                       NewGame();\r
+                       return;\r
+               }\r
+       }\r
+\r
+       US_ControlPanel();\r
+       if (restartgame)\r
+       {\r
+               playstate = ex_resetgame;\r
+       }\r
+       else if (loadedgame)\r
+       {\r
+               playstate = ex_loadedgame;\r
+       }\r
+}\r
+\r
+/*\r
+=============================================================================\r
+\r
+                                                       STAR WARS TEXT CRAWL\r
+\r
+=============================================================================\r
+*/\r
+\r
+/*\r
+============================\r
+=\r
+= BuildBitTables\r
+=\r
+============================\r
+*/\r
+\r
+void BuildBitTables(void)\r
+{\r
+       Uint16 bit1, bit2, i;\r
+       Uint8 far *buffer;\r
+\r
+       MM_GetPtr(&bittables, 0x4000);\r
+       buffer = bittables;\r
+\r
+       //\r
+       // generate a lookup table that maps the bits of the "texture" (bit1)\r
+       // to the appropriate bit for the screen position (bit2) to make the\r
+       // scaler code faster and smaller\r
+       //\r
+       // table[((7-b1)*8+(7-b2))*256+i] = (i & (1 << (7-b1))) ? (1 << (7-b2)) : 0;\r
+       //\r
+       for (bit1 = 1; bit1 < 0x100; bit1 <<= 1)\r
+       {\r
+               for (bit2 = 1; bit2 < 0x100; bit2 <<= 1)\r
+               {\r
+                       for (i = 0; i < 0x100; i++, buffer++)\r
+                       {\r
+                               if (i & bit1)\r
+                               {\r
+                                       *buffer = bit2;\r
+                               }\r
+                               else\r
+                               {\r
+                                       *buffer = 0;\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= CompileSWUpdate\r
+=\r
+============================\r
+*/\r
+\r
+void CompileSWUpdate(void)\r
+{\r
+       Sint16 y;\r
+       Uint16 i, width, scalestep, step;\r
+       Sint32 scale, rowof, xpos, size;\r
+       void far *buffer;\r
+       Uint8 srcoff, srcbit, bitpos;\r
+       Uint16 destoff, srcx, left, orindex, lastoff;\r
+\r
+       BuildBitTables();\r
+       size = 190000;\r
+       MM_GetPtr(&linecode, size);\r
+       buffer = linecode;\r
+       //\r
+       // Note: You should really lock the pointer to prevent the memmory manager\r
+       // from moving the buffer around. This code stores a bunch of pointers to\r
+       // this memory block in the linestarts array. Those pointers will not be\r
+       // updated when the memory manager moves the buffer around and the game\r
+       // might end up crashing (or worse) when trying to run the "code" at the\r
+       // memory location after the data was moved. The game starts playing music\r
+       // after this function is done, which may or may not cause the memory\r
+       // manager to move memory blocks around.\r
+       //\r
+\r
+       //\r
+       // move the buffer address into ES:DI (and keep it there)\r
+       //\r
+       asm     mov     es, WORD PTR buffer+2;\r
+       asm     mov     di, WORD PTR buffer;\r
+       //\r
+       // Since the address is kept in ES:DI, we must save and restore\r
+       // the ES register when calling other functions (push es / pop es).\r
+       // The Borland C compiler always saves and restores the DI register\r
+       // when a function modifies it, so we don't need to worry about\r
+       // that register. This is a bit of an ugly hack, but it makes this\r
+       // code a little faster and smaller.\r
+       //\r
+\r
+       scale = 320l << 11;\r
+       scalestep = (((Uint32)(320-40) << 11) / 200);   // roughly 1.4 pixels per step, going from 320 pixels to 40 pixels in 200 steps\r
+       rowof = 0;\r
+\r
+       for (y=199; y >= 0; y--)\r
+       {\r
+               //\r
+               // draw a blue line for the current row\r
+               //\r
+               asm     push    es;\r
+               VW_Hlin(0, 320, y, BLUE);\r
+               asm     pop     es;\r
+\r
+               //\r
+               // update the buffer variable with the current (normalized) ES:DI address\r
+               //\r
+               asm     mov     WORD PTR buffer, di;\r
+               asm     mov     WORD PTR buffer+2, es;\r
+\r
+               //\r
+               // store the address in the scaler lookup table\r
+               //\r
+               linestarts[y] = buffer;\r
+\r
+               //\r
+               // get current scaling factors\r
+               //\r
+               width = ((Uint16)((scale/2) >> 11)) << 1;       // some trickery to make sure width is even\r
+               sourceline[y] = (rowof >> 11);\r
+               step = (336l << 11) / width;\r
+               xpos = 0;\r
+               rowof += step;\r
+               left = 160 - (width >> 1);\r
+               destoff = ylookup[y] + left / 8;\r
+               bitpos = left & 7;\r
+\r
+               //\r
+               // generate the machine code\r
+               //\r
+               //              MOV     CX, SS\r
+               //              MOV     SS, AX\r
+               //              ADD     DI, <destoff>\r
+               //              XOR     AL, AL\r
+               //\r
+               asm     mov     ax, 0D18Ch;\r
+               asm     stosw;\r
+               asm     mov     ax, 0D08Eh;\r
+               asm     stosw;\r
+               asm     mov     ax, 0C781h;\r
+               asm     stosw;\r
+               asm     mov     ax, destoff;\r
+               asm     stosw;\r
+               asm     mov     ax, 0C030h;\r
+               asm     stosw;\r
+\r
+               lastoff = -1;\r
+               for (i=0; i<width; i++)\r
+               {\r
+                       srcx = (xpos >> 11);\r
+                       srcoff = (srcx / 8);\r
+                       srcbit = srcx & 7;\r
+\r
+                       orindex = ((7-srcbit)*8 + 7-bitpos) << 8;\r
+                       if (srcoff != lastoff)\r
+                       {\r
+                               //\r
+                               //              MOV     BL, [BP + <srcoff>]\r
+                               //\r
+                               asm     mov     ax, 5E8Ah;\r
+                               asm     stosw;\r
+                               asm     mov     al, srcoff;\r
+                               asm     stosb;\r
+\r
+                               lastoff = srcoff;\r
+                       }\r
+\r
+                       //\r
+                       //              OR              AL, [BX + <orindex>]\r
+                       //\r
+                       asm     mov     ax, 870Ah;\r
+                       asm     stosw;\r
+                       asm     mov     ax, orindex;\r
+                       asm     stosw;\r
+\r
+                       bitpos++;\r
+                       if (bitpos == 8)\r
+                       {\r
+                               bitpos = 0;\r
+\r
+                               //\r
+                               //              STOSB\r
+                               //              XOR     AL, AL\r
+                               //\r
+                               asm     mov     ax, 30AAh;\r
+                               asm     stosw;\r
+                               asm     mov     al, 0C0h;\r
+                               asm     stosb;\r
+                       }\r
+\r
+                       xpos += step;\r
+               }\r
+\r
+               if (bitpos)\r
+               {\r
+                       //\r
+                       //              STOSB\r
+                       //\r
+                       asm     mov     al, 0AAh;\r
+                       asm     stosb;\r
+               }\r
+               //\r
+               // generate end of subroutine\r
+               //\r
+               //              MOV     SS, CX\r
+               //              RETF\r
+               //\r
+               asm     mov     ax, 0D18Eh;\r
+               asm     stosw;\r
+               asm     mov     al, 0CBh;\r
+               asm     stosb;\r
+\r
+               //\r
+               // normalize ES:DI\r
+               //\r
+               asm     mov     ax, di;\r
+               asm     shr     ax, 1;\r
+               asm     shr     ax, 1;\r
+               asm     shr     ax, 1;\r
+               asm     shr     ax, 1;\r
+               asm     mov     bx, es;\r
+               asm     add     ax, bx;\r
+               asm     mov     es, ax;\r
+               asm     and     di, 0Fh;\r
+\r
+               //\r
+               // update scale value for next row\r
+               //\r
+               scale -= scalestep;\r
+\r
+               //\r
+               // replace the blue line with the row from the background image\r
+               //\r
+               asm     push    es;\r
+               VW_ScreenToScreen(ylookup[y] + 0x8000, ylookup[y], 40, 1);\r
+               asm     pop     es;\r
+\r
+               if (LastScan)\r
+                       return;\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= TranslateString\r
+=\r
+============================\r
+*/\r
+\r
+void TranslateString(char *text)\r
+{\r
+       char c;\r
+\r
+       while (*text)\r
+       {\r
+               c = *text;\r
+\r
+               if (c >= 'A' && c <= 'Z')\r
+               {\r
+                       c = c + -33;\r
+               }\r
+               else if (c >= 'a' && c <= 'z')\r
+               {\r
+                       c = c + -39;\r
+               }\r
+               else if (c == '.')\r
+               {\r
+                       c = 84;\r
+               }\r
+               else if (c == ',')\r
+               {\r
+                       c = 85;\r
+               }\r
+               else if (c == '-')\r
+               {\r
+                       c = 86;\r
+               }\r
+               else if (c == '"')\r
+               {\r
+                       c = 87;\r
+               }\r
+               else if (c == ' ')\r
+               {\r
+                       c = 88;\r
+               }\r
+               else if (c == '!')\r
+               {\r
+                       c = 89;\r
+               }\r
+               else if (c == '\'')\r
+               {\r
+                       c = 90;\r
+               }\r
+               else if (c != '\n')\r
+               {\r
+                       c = 84; // any unhandled character is drawn as '.'\r
+               }\r
+\r
+               *text++ = c;\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= DrawSWText\r
+=\r
+============================\r
+*/\r
+\r
+void DrawSWText(void)\r
+{\r
+       char far *text;\r
+       char *ptr;\r
+       char c;\r
+       char strbuf[80];\r
+\r
+       WindowX = 0;\r
+       WindowW = 336;\r
+       PrintY = 1;             // always leave the first line blank\r
+       bufferofs = 0;\r
+       panadjust = 0;\r
+       text = swtext;\r
+       masterlines = 0;\r
+\r
+       //\r
+       // draw the entire text to video memory\r
+       //\r
+       while (*text)\r
+       {\r
+               ptr = strbuf;\r
+               do\r
+               {\r
+                       c = *text++;\r
+                       *ptr++ = c;\r
+               } while (c != '\n' && c != '\0');\r
+               *ptr = '\0';\r
+\r
+               TranslateString(strbuf);\r
+\r
+               US_CPrint(strbuf);\r
+\r
+               bufferofs += ylookup[PrintY];\r
+               masterlines += PrintY;\r
+               PrintY = 0;\r
+       }\r
+\r
+       //\r
+       // allocate a buffer large enough to hold the entire text image\r
+       // and move the image data from video memory into that buffer\r
+       //\r
+       MM_GetPtr(&sourcepic, bufferofs);\r
+       EGAREADMAP(1);  // read from "green" plane (doesn't really matter from which plane we read)\r
+       movedata(screenseg, 0, FP_SEG(sourcepic), 0, bufferofs);\r
+\r
+       //\r
+       // erase the (first screen of the) text from video memory.\r
+       // we're going to display this area and copy the backgound pic\r
+       // here line-by-line as the scalers are generated and we don't\r
+       // want to have parts of the text still visible at that point.\r
+       //\r
+       bufferofs = 0;\r
+       VW_Bar(0, 0, 320, 200, BLACK);\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= ScrollSWText\r
+=\r
+============================\r
+*/\r
+\r
+void ScrollSWText(void)\r
+{\r
+       Sint32 now;\r
+       Uint16 pos;\r
+       Sint16 i, rowof;\r
+\r
+       tics = TimeCount = lasttimecount = 0;\r
+\r
+       EGAWRITEMODE(0);\r
+       EGAMAPMASK(8);  // only draw to the "intensity" plane (so we don't erase the backgound pic)\r
+\r
+       pos = 0;\r
+       while (masterlines + 400 >= pos)\r
+       {\r
+               for (i = 199; i >= 0; i--)\r
+               {\r
+                       rowof = pos - sourceline[i];\r
+                       if (rowof < 0 || rowof >= masterlines)\r
+                       {\r
+                               masterofs = 0;  // draw the first (blank) line of the buffer\r
+                       }\r
+                       else\r
+                       {\r
+                               masterofs = rowof * 42;\r
+                       }\r
+                       routine = linestarts[i];\r
+                       asm {\r
+                               mov     es, screenseg;\r
+                               mov     di, pageofs;\r
+                               mov     ds, bittables;\r
+                               push    bp;\r
+                               mov     bp, ss:masterofs;\r
+                               mov     ax, ss:sourcepic;\r
+                               xor     bh, bh;\r
+                               cli;    // disable interrupts (scaler changes register SS, so interrupts would be fatal!)\r
+                               call    ss:routine;\r
+                               sti;    // enable interrupts again\r
+                               pop     bp;\r
+                               mov     ax, ss;\r
+                               mov     ds, ax;\r
+                       }\r
+               }\r
+\r
+               VW_SetScreen(pageofs, 0);\r
+               pageon ^= 1;\r
+               pageofs = pageon << 15;\r
+\r
+               now = TimeCount;\r
+               tics = tics + (now - lasttimecount);\r
+               lasttimecount = now;\r
+               if (tics > 20)\r
+                       tics = 20;\r
+\r
+               pos = pos + tics / 4;\r
+               tics &= 3;\r
+\r
+               if (IN_IsUserInput() && LastScan != sc_F1)\r
+                       LastScan = sc_Space;\r
+\r
+               if (LastScan)\r
+                       break;\r
+       }\r
+}\r
+\r
+/*\r
+============================\r
+=\r
+= StarWars\r
+=\r
+============================\r
+*/\r
+\r
+void StarWars(void)\r
+{\r
+       SetPaletteEx(colors[0]);        // all black\r
+       VW_ClearVideo(BLACK);\r
+       VW_SetLineWidth(42);    // 336 pixels\r
+       VW_SetScreen(0, 0);\r
+       pageon = pageofs = 0;\r
+       CA_SetAllPurge();\r
+       CA_CacheGrChunk(STARTFONT+2);\r
+       fontnumber = 2;\r
+       DrawSWText();\r
+       fontnumber = 0;\r
+\r
+       CA_CacheGrChunk(SW_BACKGROUNDPIC);\r
+       bufferofs = 0x8000;\r
+       VW_DrawPic(0, 0, SW_BACKGROUNDPIC);\r
+       CA_SetAllPurge();\r
+       SetPaletteEx(starcolors);\r
+       bufferofs = 0;\r
+       CompileSWUpdate();\r
+\r
+       if (!LastScan)\r
+       {\r
+               StartMusic(STARWARSMUSIC);\r
+               ScrollSWText();\r
+               StopMusic();\r
+       }\r
+\r
+       MM_FreePtr(&linecode);\r
+       MM_FreePtr(&bittables);\r
+       MM_FreePtr(&sourcepic);\r
+\r
+       VW_ClearVideo(BLACK);\r
+       VW_SetLineWidth(SCREENWIDTH);\r
+       VW_SetDefaultColors();\r
+       RF_FixOfs();\r
+       CA_ClearMarks();\r
+\r
+       CheckLastScan();\r
+}\r
+\r
+#endif // if GRMODE == EGAGR\r
+\r
+//===========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= ShowTitle\r
+=\r
+============================\r
+*/\r
+\r
+void ShowTitle(void)\r
+{\r
+       panadjust = 0;\r
+       CA_CacheGrChunk(TITLEPICPIC);\r
+       VW_DrawPic(0, 0, TITLEPICPIC);\r
+#if GRMODE == CGAGR\r
+       VW_UpdateScreen();\r
+#else\r
+       VW_SetScreen(displayofs, 0);\r
+       VW_ScreenToScreen(bufferofs, displayofs, 42, 224);\r
+#endif\r
+       IN_UserInput(6*TickBase, false);\r
+       CA_ClearMarks();\r
+       CheckLastScan();\r
+}\r
+\r
+//===========================================================================\r
+\r
+#if GRMODE == CGAGR\r
+/*\r
+============================\r
+=\r
+= ShowCredits\r
+=\r
+============================\r
+*/\r
+\r
+void ShowCredits(void)\r
+{\r
+       panadjust = 0;\r
+       CA_CacheGrChunk(SW_BACKGROUNDPIC);\r
+       VW_DrawPic(0, 0, SW_BACKGROUNDPIC);\r
+       VW_UpdateScreen();\r
+       IN_UserInput(6*TickBase, false);\r
+       CA_ClearMarks();\r
+       CheckLastScan();\r
+}\r
+#endif\r
+\r
+//===========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= RunDemo\r
+=\r
+============================\r
+*/\r
+\r
+void RunDemo(Sint16 num)\r
+{\r
+       Uint16 far *demodata;\r
+       \r
+       NewGame();\r
+       num += DEMO0;\r
+       CA_CacheGrChunk(num);\r
+       demodata = grsegs[num];\r
+       gamestate.mapon = demodata[0];\r
+       DemoSize = demodata[1];\r
+       MM_GetPtr(&(memptr)DemoBuffer, DemoSize);\r
+       MM_SetLock(&(memptr)DemoBuffer, true);\r
+       _fmemcpy(DemoBuffer, ((char _seg *)grsegs[num])+4, DemoSize);\r
+       MM_FreePtr(&grsegs[num]);\r
+       IN_StartDemoPlayback(DemoBuffer, DemoSize);\r
+       SetupGameLevel(true);\r
+       if (scorescreenkludge)\r
+       {\r
+               DrawHighScores();\r
+       }\r
+       PlayLoop();\r
+       IN_StopDemo();\r
+       MM_FreePtr(&(memptr)DemoBuffer);\r
+       VW_FixRefreshBuffer();\r
+       CA_ClearMarks();\r
+       CheckLastScan();\r
+}\r
+\r
+//===========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= DrawHighScores\r
+=\r
+============================\r
+*/\r
+\r
+void DrawHighScores(void)\r
+{\r
+       Uint16 i, n;\r
+       Uint16 width, height;\r
+       HighScore *entry;\r
+       Uint16 oldbufferofs;\r
+       char buf[16], *bufptr;\r
+       \r
+       RF_NewPosition(0, 0);\r
+       oldbufferofs = bufferofs;\r
+       bufferofs = masterofs;\r
+#ifdef KEEN5\r
+#if GRMODE == CGAGR\r
+       fontcolor = 2;\r
+#else\r
+       fontcolor = BLUE ^ LIGHTMAGENTA;        // blue text on light magenta background (XOR draw mode!)\r
+#endif\r
+#endif\r
+       for (i=0, entry=&Scores[0]; i<MaxScores; i++, entry++)\r
+       {\r
+               PrintY = i*16 + HIGHSCORE_TOP;\r
+               PrintX = HIGHSCORE_LEFT;\r
+               US_Print(entry->name);\r
+#ifdef KEEN4\r
+               PrintX = 152;\r
+               for (n=0; n<entry->completed; n++)\r
+               {\r
+                       VWB_DrawTile8(PrintX, PrintY+1, 71);\r
+                       PrintX += 8;\r
+               }\r
+#endif\r
+               ultoa(entry->score, buf, 10);\r
+               for (bufptr=buf; *bufptr; bufptr++)\r
+               {\r
+                       *bufptr = *bufptr + 81;\r
+               }\r
+               USL_MeasureString(buf, &width, &height);\r
+               PrintX = HIGHSCORE_RIGHT - width;\r
+               US_Print(buf);\r
+       }\r
+       fontcolor = WHITE;      // back to default color\r
+       bufferofs = oldbufferofs;\r
+}\r
+\r
+//===========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= CheckHighScore\r
+=\r
+============================\r
+*/\r
+\r
+void CheckHighScore(Sint32 score, Sint16 completed)\r
+{\r
+       Uint16 i, n;\r
+       Sint16 index;\r
+       HighScore entry;\r
+       \r
+       strcpy(entry.name, ""); //Note: 'entry.name[0] = 0;' would be more efficient\r
+       entry.score = score;\r
+       entry.completed = completed;\r
+       for (i=0, index=-1; i<MaxScores; i++)\r
+       {\r
+               if (Scores[i].score < entry.score ||\r
+                       (Scores[i].score == entry.score && Scores[i].completed < entry.completed))\r
+               {\r
+                       n=MaxScores;\r
+                       while (--n > i)\r
+                       {\r
+                               Scores[n] = Scores[n-1];\r
+                       }\r
+                       Scores[i] = entry;\r
+                       index = i;\r
+                       HighScoresDirty = true;\r
+                       break;\r
+               }\r
+       }\r
+       if (index != -1)\r
+       {\r
+               scorescreenkludge = true;\r
+               gamestate.mapon = HIGHSCORE_MAP;\r
+               SetupGameLevel(true);\r
+               DrawHighScores();\r
+#ifdef KEEN5\r
+#if GRMODE == CGAGR\r
+               fontcolor = 2;\r
+#else\r
+               fontcolor = BLUE ^ LIGHTMAGENTA;        // blue text on light magenta background (XOR draw mode!)\r
+#endif\r
+#endif\r
+               RF_Refresh();\r
+               RF_Refresh();\r
+               PrintY = i*16 + HIGHSCORE_TOP;\r
+               PrintX = HIGHSCORE_LEFT;\r
+               US_LineInput(PrintX, PrintY, Scores[index].name, NULL, true, MaxHighName, 112);\r
+               scorescreenkludge = false;\r
+       }\r
+#ifdef KEEN5\r
+       fontcolor = 15; // back to default color (white)\r
+#endif\r
+}\r
+\r
+//===========================================================================\r
+\r
+/*\r
+============================\r
+=\r
+= ShowHighScores\r
+=\r
+============================\r
+*/\r
+\r
+void ShowHighScores(void)\r
+{\r
+       scorescreenkludge = true;\r
+       IN_ClearKeysDown();\r
+       RunDemo(4);\r
+       scorescreenkludge = false;\r
+}\r