void intro()
{
    // following overlayed data safe if "putstrings" are resident
#ifdef XFRACT
    static char PRESS_ENTER[] = {"Press ENTER for main menu, Shift-1 for help."};
#else
    static char PRESS_ENTER[] = {"Press ENTER for main menu, F1 for help."};
#endif
    int       toprow, botrow, delaymax;
    char      oldchar;
    std::vector<int> authors;
    char credits[32768] = { 0 };
    char screen_text[32768];
    int       oldlookatmouse;
    int       oldhelpmode;

    timer_start -= clock_ticks();       // "time out" during help
    oldlookatmouse = lookatmouse;
    oldhelpmode = helpmode;
    lookatmouse = 0;                    // de-activate full mouse checking

    int i = 32767 + read_help_topic(INTRO_AUTHORS, 0, 32767, screen_text);
    screen_text[i++] = '\0';
    i = 32767 + read_help_topic(INTRO_CREDITS, 0, 32767, credits);
    credits[i++] = '\0';

    int j = 0;
    authors.push_back(0);               // find the start of each credit-line
    for (i = 0; credits[i] != 0; i++)
        if (credits[i] == 10)
            authors.push_back(i+1);
    authors.push_back(i);

    helptitle();
#define END_MAIN_AUTHOR 5
    toprow = END_MAIN_AUTHOR+1;
#ifndef XFRACT
    botrow = 21;
#else
    botrow = 20;
    putstringcenter(21, 0, 80, C_TITLE,
                    "Unix/X port of fractint by Ken Shirriff");
#endif
    putstringcenter(1, 0, 80, C_TITLE, PRESS_ENTER);
    driver_put_string(2, 0, C_CONTRIB, screen_text);
    driver_set_attr(2, 0, C_AUTHDIV1, 80);
    driver_set_attr(END_MAIN_AUTHOR, 0, C_AUTHDIV1, 80);
    driver_set_attr(22, 0, C_AUTHDIV2, 80);
    driver_set_attr(3, 0, C_PRIMARY, 80*(END_MAIN_AUTHOR-3));
    driver_set_attr(23, 0, C_TITLE_LOW, 160);
    for (int i = 3; i < END_MAIN_AUTHOR; ++i)
        driver_set_attr(i, 21, C_CONTRIB, 58);
    driver_set_attr(toprow, 0, C_CONTRIB, (21-END_MAIN_AUTHOR)*80);
    srand((unsigned int)clock_ticks());
    j = rand()%(j-(botrow-toprow)); // first to use
    i = j+botrow-toprow; // last to use
    oldchar = credits[authors.at(i+1)];
    credits[authors.at(i+1)] = 0;
    driver_put_string(toprow, 0, C_CONTRIB, credits+authors.at(j));
    credits[authors.at(i+1)] = oldchar;
    delaymax = 10;
    driver_hide_text_cursor();
    helpmode = HELPMENU;
    while (! driver_key_pressed())
    {
#ifdef XFRACT
        if (slowdisplay)
            delaymax *= 15;
#endif
        for (j = 0; j < delaymax && !(driver_key_pressed()); j++)
            driver_delay(100);
        if (driver_key_pressed() == FIK_SPACE)
        {   // spacebar pauses
            driver_get_key();
            driver_wait_key_pressed(0);
            if (driver_key_pressed() == FIK_SPACE)
                driver_get_key();
        }
        delaymax = 15;
        driver_scroll_up(toprow, botrow);
        i++;
        if (credits[authors.at(i)] == 0)
            i = 0;
        oldchar = credits[authors.at(i+1)];
        credits[authors.at(i+1)] = 0;
        driver_put_string(botrow, 0, C_CONTRIB, &credits[authors.at(i)]);
        driver_set_attr(botrow, 0, C_CONTRIB, 80);
        credits[authors.at(i+1)] = oldchar;
        driver_hide_text_cursor(); // turn it off
    }

    lookatmouse = oldlookatmouse;                // restore the mouse-checking
    helpmode = oldhelpmode;
    return;
}
void intro()
{
    // following overlayed data safe if "putstrings" are resident
    static char PRESS_ENTER[] = {"Press ENTER for main menu, F1 for help."};
    int       toprow, botrow, delaymax;
    char      oldchar;
    std::vector<int> authors;
    char credits[32768] = { 0 };
    char screen_text[32768];
    int old_look_at_mouse;

    g_timer_start -= std::clock();       // "time out" during help
    old_look_at_mouse = g_look_at_mouse;
    help_labels const old_help_mode = g_help_mode;
    g_look_at_mouse = 0;                    // de-activate full mouse checking

    int i = 32767 + read_help_topic(help_labels::INTRO_AUTHORS, 0, 32767, screen_text);
    screen_text[i] = '\0';
    i = 32767 + read_help_topic(help_labels::INTRO_CREDITS, 0, 32767, credits);
    credits[i] = '\0';

    int j = 0;
    authors.push_back(0);               // find the start of each credit-line
    for (i = 0; credits[i] != 0; i++)
    {
        if (credits[i] == '\n')
        {
            authors.push_back(i+1);
        }
    }
    authors.push_back(i);

    helptitle();
#define END_MAIN_AUTHOR 5
    toprow = END_MAIN_AUTHOR+1;
    botrow = 21;
    putstringcenter(1, 0, 80, C_TITLE, PRESS_ENTER);
    driver_put_string(2, 0, C_CONTRIB, screen_text);
    driver_set_attr(2, 0, C_AUTHDIV1, 80);
    driver_set_attr(END_MAIN_AUTHOR, 0, C_AUTHDIV1, 80);
    driver_set_attr(22, 0, C_AUTHDIV2, 80);
    driver_set_attr(3, 0, C_PRIMARY, 80*(END_MAIN_AUTHOR-3));
    driver_set_attr(23, 0, C_TITLE_LOW, 160);

    for (int i = 3; i < END_MAIN_AUTHOR; ++i)
    {
        driver_set_attr(i, 21, C_CONTRIB, 58);
    }
    driver_set_attr(toprow, 0, C_CONTRIB, (21-END_MAIN_AUTHOR)*80);
    srand((unsigned int)std::clock());
    j = rand()%(j-(botrow-toprow)); // first to use
    i = j+botrow-toprow; // last to use
    oldchar = credits[authors.at(i+1)];
    credits[authors.at(i+1)] = 0;
    driver_put_string(toprow, 0, C_CONTRIB, credits+authors.at(j));
    credits[authors.at(i+1)] = oldchar;
    delaymax = 10;
    driver_hide_text_cursor();
    g_help_mode = help_labels::HELPMENU;
    while (! driver_key_pressed())
    {
        if (slowdisplay)
        {
            delaymax *= 15;
        }
        for (j = 0; j < delaymax && !(driver_key_pressed()); j++)
        {
            driver_delay(100);
        }
        if (driver_key_pressed() == FIK_SPACE)
        {
            // spacebar pauses
            driver_get_key();
            driver_wait_key_pressed(0);
            if (driver_key_pressed() == FIK_SPACE)
            {
                driver_get_key();
            }
        }
        delaymax = 15;
        driver_scroll_up(toprow, botrow);
        i++;
        if (credits[authors.at(i)] == 0)
        {
            i = 0;
        }
        oldchar = credits[authors.at(i+1)];
        credits[authors.at(i+1)] = 0;
        driver_put_string(botrow, 0, C_CONTRIB, &credits[authors.at(i)]);
        driver_set_attr(botrow, 0, C_CONTRIB, 80);
        credits[authors.at(i+1)] = oldchar;
        driver_hide_text_cursor(); // turn it off
    }

    g_look_at_mouse = old_look_at_mouse;                // restore the mouse-checking
    g_help_mode = old_help_mode;
}
int common_startdisk(long newrowsize, long newcolsize, int colors)
{
    int freemem;
    long memorysize;
    unsigned int *fwd_link = nullptr;
    long longtmp;
    unsigned int cache_size;
    BYTE *tempfar = nullptr;
    if (g_disk_flag)
    {
        enddisk();
    }
    if (driver_diskp()) // otherwise, real screen also in use, don't hit it
    {
        char buf[128];
        helptitle();
        driver_set_attr(1, 0, C_DVID_BKGRD, 24*80);  // init rest to background
        for (int i = 0; i < BOXDEPTH; ++i)
        {
            driver_set_attr(BOXROW+i, BOXCOL, C_DVID_LO, BOXWIDTH);  // init box
        }
        driver_put_string(BOXROW+2, BOXCOL+4, C_DVID_HI, "'Disk-Video' mode");
        sprintf(buf, "Screen resolution: %d x %d", sxdots, sydots);
        driver_put_string(BOXROW+4, BOXCOL+4, C_DVID_LO, buf);
        if (disktarga)
        {
            driver_put_string(-1, -1, C_DVID_LO, "  24 bit Targa");
        }
        else
        {
            driver_put_string(-1, -1, C_DVID_LO, "  Colors: ");
            sprintf(buf, "%d", colors);
            driver_put_string(-1, -1, C_DVID_LO, buf);
        }
        sprintf(buf, "Save name: %s", savename);
        driver_put_string(BOXROW+8, BOXCOL+4, C_DVID_LO, buf);
        driver_put_string(BOXROW+10, BOXCOL+4, C_DVID_LO, "Status:");
        dvid_status(0, "clearing the 'screen'");
    }
    high_offset = -1;
    seek_offset = high_offset;
    cur_offset = seek_offset;
    cur_row    = -1;
    if (disktarga)
    {
        pixelshift = 0;
    }
    else
    {
        pixelshift = 3;
        int i = 2;
        while (i < colors)
        {
            i *= i;
            --pixelshift;
        }
    }
    timetodisplay = bf_math != bf_math_type::NONE ? 10 : 1000;  // time-to-g_driver-status counter

    /* allocate cache: try for the max; leave FREEMEMk free if we can get
        that much or more; if we can't get that much leave 1/2 of whatever
        there is free; demand a certain minimum or nogo at all */
    freemem = FREEMEM;

    for (cache_size = CACHEMAX; cache_size >= CACHEMIN; --cache_size)
    {
        longtmp = ((int)cache_size < freemem) ?
                  (long)cache_size << 11 : (long)(cache_size+freemem) << 10;
        tempfar = static_cast<BYTE *>(malloc(longtmp));
        if (tempfar != nullptr)
        {
            free(tempfar);
            break;
        }
    }
    if (debugflag == debug_flags::force_disk_min_cache)
    {
        cache_size = CACHEMIN;
    }
    longtmp = (long)cache_size << 10;
    cache_start = (cache *)malloc(longtmp);
    if (cache_size == 64)
    {
        --longtmp; // safety for next line
    }
    cache_lru = cache_start;
    cache_end = cache_lru + longtmp/sizeof(*cache_start);
    membuf = (BYTE *)malloc((long)BLOCKLEN);
    if (cache_start == nullptr || membuf == nullptr)
    {
        stopmsg(STOPMSG_NONE,
            "*** insufficient free memory for cache buffers ***");
        return -1;
    }
    if (driver_diskp())
    {
        char buf[50];
        sprintf(buf, "Cache size: %dK", cache_size);
        driver_put_string(BOXROW+6, BOXCOL+4, C_DVID_LO, buf);
    }

    // preset cache to all invalid entries so we don't need free list logic
    for (int i = 0; i < HASHSIZE; ++i)
    {
        hash_ptr[i] = 0xffff; // 0xffff marks the end of a hash chain
    }
    longtmp = 100000000L;
    for (cache *ptr1 = cache_start; ptr1 < cache_end; ++ptr1)
    {
        ptr1->dirty = false;
        ptr1->lru = false;
        longtmp += BLOCKLEN;
        fwd_link = &hash_ptr[(((unsigned short)longtmp >> BLOCKSHIFT) & (HASHSIZE-1))];
        ptr1->offset = longtmp;
        ptr1->hashlink = *fwd_link;
        *fwd_link = (int)((char *)ptr1 - (char *)cache_start);
    }

    memorysize = (long)(newcolsize) * newrowsize + headerlength;
    int i = (short)memorysize & (BLOCKLEN-1);
    if (i != 0)
    {
        memorysize += BLOCKLEN - i;
    }
    memorysize >>= pixelshift;
    memorysize >>= BLOCKSHIFT;
    g_disk_flag = true;
    rowsize = (unsigned int) newrowsize;
    colsize = (unsigned int) newcolsize;

    if (disktarga)
    {
        // Retrieve the header information first
        BYTE *tmpptr;
        tmpptr = membuf;
        fseek(fp, 0L, SEEK_SET);
        for (int i = 0; i < headerlength; i++)
        {
            *tmpptr++ = (BYTE)fgetc(fp);
        }
        fclose(fp);
        dv_handle = MemoryAlloc((U16)BLOCKLEN, memorysize, DISK);
    }
    else
    {
        dv_handle = MemoryAlloc((U16)BLOCKLEN, memorysize, MEMORY);
    }
    if (dv_handle == 0)
    {
        stopmsg(STOPMSG_NONE, "*** insufficient free memory/disk space ***");
        g_good_mode = false;
        rowsize = 0;
        return -1;
    }

    if (driver_diskp())
    {
        driver_put_string(BOXROW+2, BOXCOL+23, C_DVID_LO,
                          (MemoryType(dv_handle) == DISK) ? "Using your Disk Drive" : "Using your memory");
    }

    membufptr = membuf;

    if (disktarga)
    {
        // Put header information in the file
        MoveToMemory(membuf, (U16)headerlength, 1L, 0, dv_handle);
    }
    else
    {
        for (long offset = 0; offset < memorysize; offset++)
        {
            SetMemory(0, (U16)BLOCKLEN, 1L, offset, dv_handle);
            if (driver_key_pressed())           // user interrupt
            {
                // esc to cancel, else continue
                if (stopmsg(STOPMSG_CANCEL, "Disk Video initialization interrupted:\n"))
                {
                    enddisk();
                    g_good_mode = false;
                    return -2;            // -1 == failed, -2 == cancel
                }
            }
        }
    }

    if (driver_diskp())
    {
        dvid_status(0, "");
    }
    return 0;
}