win_update(win_T *wp)
{
    buf_T	*buf = wp->w_buffer;
    int		type;
    int		top_end = 0;	// Below last row of the top area that needs
				// updating.  0 when no top area updating.
    int		mid_start = 999;// first row of the mid area that needs
				// updating.  999 when no mid area updating.
    int		mid_end = 0;	// Below last row of the mid area that needs
				// updating.  0 when no mid area updating.
    int		bot_start = 999;// first row of the bot area that needs
				// updating.  999 when no bot area updating
    int		scrolled_down = FALSE;	// TRUE when scrolled down when
					// w_topline got smaller a bit
#ifdef FEAT_SEARCH_EXTRA
    int		top_to_mod = FALSE;    // redraw above mod_top
#endif

    int		row;		// current window row to display
    linenr_T	lnum;		// current buffer lnum to display
    int		idx;		// current index in w_lines[]
    int		srow;		// starting row of the current line

    int		eof = FALSE;	// if TRUE, we hit the end of the file
    int		didline = FALSE; // if TRUE, we finished the last line
    int		i;
    long	j;
    static int	recursive = FALSE;	// being called recursively
    linenr_T	old_botline = wp->w_botline;
#ifdef FEAT_CONCEAL
    int		old_wrow = wp->w_wrow;
    int		old_wcol = wp->w_wcol;
#endif
#ifdef FEAT_FOLDING
    long	fold_count;
#endif
#ifdef FEAT_SYN_HL
    // remember what happened to the previous line, to know if
    // check_visual_highlight() can be used
#define DID_NONE 1	// didn't update a line
#define DID_LINE 2	// updated a normal line
#define DID_FOLD 3	// updated a folded line
    int		did_update = DID_NONE;
    linenr_T	syntax_last_parsed = 0;		// last parsed text line
#endif
    linenr_T	mod_top = 0;
    linenr_T	mod_bot = 0;
#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA)
    int		save_got_int;
#endif
#ifdef SYN_TIME_LIMIT
    proftime_T	syntax_tm;
#endif

#if defined(FEAT_SEARCH_EXTRA) || defined(FEAT_CLIPBOARD)
    // This needs to be done only for the first window when update_screen() is
    // called.
    if (!did_update_one_window)
    {
	did_update_one_window = TRUE;
# ifdef FEAT_SEARCH_EXTRA
	start_search_hl();
# endif
# ifdef FEAT_CLIPBOARD
	// When Visual area changed, may have to update selection.
	if (clip_star.available && clip_isautosel_star())
	    clip_update_selection(&clip_star);
	if (clip_plus.available && clip_isautosel_plus())
	    clip_update_selection(&clip_plus);
# endif
    }
#endif

    type = wp->w_redr_type;

    if (type == NOT_VALID)
    {
	wp->w_redr_status = TRUE;
	wp->w_lines_valid = 0;
    }

    // Window frame is zero-height: nothing to draw.
    if (wp->w_height + WINBAR_HEIGHT(wp) == 0
	    || (wp->w_frame->fr_height == wp->w_status_height
#if defined(FEAT_PROP_POPUP)
		&& !popup_is_popup(wp)
#endif
	       ))
    {
	wp->w_redr_type = 0;
	return;
    }

    // Window is zero-width: Only need to draw the separator.
    if (wp->w_width == 0)
    {
	// draw the vertical separator right of this window
	draw_vsep_win(wp, 0);
	wp->w_redr_type = 0;
	return;
    }

#ifdef FEAT_TERMINAL
    // If this window contains a terminal, redraw works completely differently.
    if (term_do_update_window(wp))
    {
	term_update_window(wp);
# ifdef FEAT_MENU
	// Draw the window toolbar, if there is one.
	if (winbar_height(wp) > 0)
	    redraw_win_toolbar(wp);
# endif
	wp->w_redr_type = 0;
	return;
    }
#endif

#ifdef FEAT_SEARCH_EXTRA
    init_search_hl(wp, &screen_search_hl);
#endif

#ifdef FEAT_LINEBREAK
    // Force redraw when width of 'number' or 'relativenumber' column
    // changes.
    i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0;
    if (wp->w_nrwidth != i)
    {
	type = NOT_VALID;
	wp->w_nrwidth = i;
    }
    else
#endif

    if (buf->b_mod_set && buf->b_mod_xlines != 0 && wp->w_redraw_top != 0)
    {
	// When there are both inserted/deleted lines and specific lines to be
	// redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw
	// everything (only happens when redrawing is off for while).
	type = NOT_VALID;
    }
    else
    {
	// Set mod_top to the first line that needs displaying because of
	// changes.  Set mod_bot to the first line after the changes.
	mod_top = wp->w_redraw_top;
	if (wp->w_redraw_bot != 0)
	    mod_bot = wp->w_redraw_bot + 1;
	else
	    mod_bot = 0;
	if (buf->b_mod_set)
	{
	    if (mod_top == 0 || mod_top > buf->b_mod_top)
	    {
		mod_top = buf->b_mod_top;
#ifdef FEAT_SYN_HL
		// Need to redraw lines above the change that may be included
		// in a pattern match.
		if (syntax_present(wp))
		{
		    mod_top -= buf->b_s.b_syn_sync_linebreaks;
		    if (mod_top < 1)
			mod_top = 1;
		}
#endif
	    }
	    if (mod_bot == 0 || mod_bot < buf->b_mod_bot)
		mod_bot = buf->b_mod_bot;

#ifdef FEAT_SEARCH_EXTRA
	    // When 'hlsearch' is on and using a multi-line search pattern, a
	    // change in one line may make the Search highlighting in a
	    // previous line invalid.  Simple solution: redraw all visible
	    // lines above the change.
	    // Same for a match pattern.
	    if (screen_search_hl.rm.regprog != NULL
		    && re_multiline(screen_search_hl.rm.regprog))
		top_to_mod = TRUE;
	    else
	    {
		matchitem_T *cur = wp->w_match_head;

		while (cur != NULL)
		{
		    if (cur->match.regprog != NULL
					   && re_multiline(cur->match.regprog))
		    {
			top_to_mod = TRUE;
			break;
		    }
		    cur = cur->next;
		}
	    }
#endif
	}
#ifdef FEAT_FOLDING
	if (mod_top != 0 && hasAnyFolding(wp))
	{
	    linenr_T	lnumt, lnumb;

	    // A change in a line can cause lines above it to become folded or
	    // unfolded.  Find the top most buffer line that may be affected.
	    // If the line was previously folded and displayed, get the first
	    // line of that fold.  If the line is folded now, get the first
	    // folded line.  Use the minimum of these two.

	    // Find last valid w_lines[] entry above mod_top.  Set lnumt to
	    // the line below it.  If there is no valid entry, use w_topline.
	    // Find the first valid w_lines[] entry below mod_bot.  Set lnumb
	    // to this line.  If there is no valid entry, use MAXLNUM.
	    lnumt = wp->w_topline;
	    lnumb = MAXLNUM;
	    for (i = 0; i < wp->w_lines_valid; ++i)
		if (wp->w_lines[i].wl_valid)
		{
		    if (wp->w_lines[i].wl_lastlnum < mod_top)
			lnumt = wp->w_lines[i].wl_lastlnum + 1;
		    if (lnumb == MAXLNUM && wp->w_lines[i].wl_lnum >= mod_bot)
		    {
			lnumb = wp->w_lines[i].wl_lnum;
			// When there is a fold column it might need updating
			// in the next line ("J" just above an open fold).
			if (compute_foldcolumn(wp, 0) > 0)
			    ++lnumb;
		    }
		}

	    (void)hasFoldingWin(wp, mod_top, &mod_top, NULL, TRUE, NULL);
	    if (mod_top > lnumt)
		mod_top = lnumt;

	    // Now do the same for the bottom line (one above mod_bot).
	    --mod_bot;
	    (void)hasFoldingWin(wp, mod_bot, NULL, &mod_bot, TRUE, NULL);
	    ++mod_bot;
	    if (mod_bot < lnumb)
		mod_bot = lnumb;
	}
#endif

	// When a change starts above w_topline and the end is below
	// w_topline, start redrawing at w_topline.
	// If the end of the change is above w_topline: do like no change was
	// made, but redraw the first line to find changes in syntax.
	if (mod_top != 0 && mod_top < wp->w_topline)
	{
	    if (mod_bot > wp->w_topline)
		mod_top = wp->w_topline;
#ifdef FEAT_SYN_HL
	    else if (syntax_present(wp))
		top_end = 1;
#endif
	}

	// When line numbers are displayed need to redraw all lines below
	// inserted/deleted lines.
	if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu)
	    mod_bot = MAXLNUM;
    }
    wp->w_redraw_top = 0;	// reset for next time
    wp->w_redraw_bot = 0;

    // When only displaying the lines at the top, set top_end.  Used when
    // window has scrolled down for msg_scrolled.
    if (type == REDRAW_TOP)
    {
	j = 0;
	for (i = 0; i < wp->w_lines_valid; ++i)
	{
	    j += wp->w_lines[i].wl_size;
	    if (j >= wp->w_upd_rows)
	    {
		top_end = j;
		break;
	    }
	}
	if (top_end == 0)
	    // not found (cannot happen?): redraw everything
	    type = NOT_VALID;
	else
	    // top area defined, the rest is VALID
	    type = VALID;
    }

    // Trick: we want to avoid clearing the screen twice.  screenclear() will
    // set "screen_cleared" to TRUE.  The special value MAYBE (which is still
    // non-zero and thus not FALSE) will indicate that screenclear() was not
    // called.
    if (screen_cleared)
	screen_cleared = MAYBE;

    // If there are no changes on the screen that require a complete redraw,
    // handle three cases:
    // 1: we are off the top of the screen by a few lines: scroll down
    // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up
    // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in
    //    w_lines[] that needs updating.
    if ((type == VALID || type == SOME_VALID
				  || type == INVERTED || type == INVERTED_ALL)
#ifdef FEAT_DIFF
	    && !wp->w_botfill && !wp->w_old_botfill
#endif
	    )
    {
	if (mod_top != 0
		&& wp->w_topline == mod_top
		&& (!wp->w_lines[0].wl_valid
		    || wp->w_topline <= wp->w_lines[0].wl_lnum))
	{
	    // w_topline is the first changed line and window is not scrolled,
	    // the scrolling from changed lines will be done further down.
	}
	else if (wp->w_lines[0].wl_valid
		&& (wp->w_topline < wp->w_lines[0].wl_lnum
#ifdef FEAT_DIFF
		    || (wp->w_topline == wp->w_lines[0].wl_lnum
			&& wp->w_topfill > wp->w_old_topfill)
#endif
		   ))
	{
	    // New topline is above old topline: May scroll down.
#ifdef FEAT_FOLDING
	    if (hasAnyFolding(wp))
	    {
		linenr_T ln;

		// count the number of lines we are off, counting a sequence
		// of folded lines as one
		j = 0;
		for (ln = wp->w_topline; ln < wp->w_lines[0].wl_lnum; ++ln)
		{
		    ++j;
		    if (j >= wp->w_height - 2)
			break;
		    (void)hasFoldingWin(wp, ln, NULL, &ln, TRUE, NULL);
		}
	    }
	    else
#endif
		j = wp->w_lines[0].wl_lnum - wp->w_topline;
	    if (j < wp->w_height - 2)		// not too far off
	    {
		i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);
#ifdef FEAT_DIFF
		// insert extra lines for previously invisible filler lines
		if (wp->w_lines[0].wl_lnum != wp->w_topline)
		    i += diff_check_fill(wp, wp->w_lines[0].wl_lnum)
							  - wp->w_old_topfill;
#endif
		if (i < wp->w_height - 2)	// less than a screen off
		{
		    // Try to insert the correct number of lines.
		    // If not the last window, delete the lines at the bottom.
		    // win_ins_lines may fail when the terminal can't do it.
		    if (i > 0)
			check_for_delay(FALSE);
		    if (win_ins_lines(wp, 0, i, FALSE, wp == firstwin) == OK)
		    {
			if (wp->w_lines_valid != 0)
			{
			    // Need to update rows that are new, stop at the
			    // first one that scrolled down.
			    top_end = i;
			    scrolled_down = TRUE;

			    // Move the entries that were scrolled, disable
			    // the entries for the lines to be redrawn.
			    if ((wp->w_lines_valid += j) > wp->w_height)
				wp->w_lines_valid = wp->w_height;
			    for (idx = wp->w_lines_valid; idx - j >= 0; idx--)
				wp->w_lines[idx] = wp->w_lines[idx - j];
			    while (idx >= 0)
				wp->w_lines[idx--].wl_valid = FALSE;
			}
		    }
		    else
			mid_start = 0;		// redraw all lines
		}
		else
		    mid_start = 0;		// redraw all lines
	    }
	    else
		mid_start = 0;		// redraw all lines
	}
	else
	{
	    // New topline is at or below old topline: May scroll up.
	    // When topline didn't change, find first entry in w_lines[] that
	    // needs updating.

	    // try to find wp->w_topline in wp->w_lines[].wl_lnum
	    j = -1;
	    row = 0;
	    for (i = 0; i < wp->w_lines_valid; i++)
	    {
		if (wp->w_lines[i].wl_valid
			&& wp->w_lines[i].wl_lnum == wp->w_topline)
		{
		    j = i;
		    break;
		}
		row += wp->w_lines[i].wl_size;
	    }
	    if (j == -1)
	    {
		// if wp->w_topline is not in wp->w_lines[].wl_lnum redraw all
		// lines
		mid_start = 0;
	    }
	    else
	    {
		// Try to delete the correct number of lines.
		// wp->w_topline is at wp->w_lines[i].wl_lnum.
#ifdef FEAT_DIFF
		// If the topline didn't change, delete old filler lines,
		// otherwise delete filler lines of the new topline...
		if (wp->w_lines[0].wl_lnum == wp->w_topline)
		    row += wp->w_old_topfill;
		else
		    row += diff_check_fill(wp, wp->w_topline);
		// ... but don't delete new filler lines.
		row -= wp->w_topfill;
#endif
		if (row > 0)
		{
		    check_for_delay(FALSE);
		    if (win_del_lines(wp, 0, row, FALSE, wp == firstwin, 0)
									 == OK)
			bot_start = wp->w_height - row;
		    else
			mid_start = 0;		// redraw all lines
		}
		if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0)
		{
		    // Skip the lines (below the deleted lines) that are still
		    // valid and don't need redrawing.	Copy their info
		    // upwards, to compensate for the deleted lines.  Set
		    // bot_start to the first row that needs redrawing.
		    bot_start = 0;
		    idx = 0;
		    for (;;)
		    {
			wp->w_lines[idx] = wp->w_lines[j];
			// stop at line that didn't fit, unless it is still
			// valid (no lines deleted)
			if (row > 0 && bot_start + row
				 + (int)wp->w_lines[j].wl_size > wp->w_height)
			{
			    wp->w_lines_valid = idx + 1;
			    break;
			}
			bot_start += wp->w_lines[idx++].wl_size;

			// stop at the last valid entry in w_lines[].wl_size
			if (++j >= wp->w_lines_valid)
			{
			    wp->w_lines_valid = idx;
			    break;
			}
		    }
#ifdef FEAT_DIFF
		    // Correct the first entry for filler lines at the top
		    // when it won't get updated below.
		    if (wp->w_p_diff && bot_start > 0)
			wp->w_lines[0].wl_size =
			    plines_win_nofill(wp, wp->w_topline, TRUE)
							      + wp->w_topfill;
#endif
		}
	    }
	}

	// When starting redraw in the first line, redraw all lines.  When
	// there is only one window it's probably faster to clear the screen
	// first.
	if (mid_start == 0)
	{
	    mid_end = wp->w_height;
	    if (ONE_WINDOW && !WIN_IS_POPUP(wp))
	    {
		// Clear the screen when it was not done by win_del_lines() or
		// win_ins_lines() above, "screen_cleared" is FALSE or MAYBE
		// then.
		if (screen_cleared != TRUE)
		    screenclear();
		// The screen was cleared, redraw the tab pages line.
		if (redraw_tabline)
		    draw_tabline();
	    }
	}

	// When win_del_lines() or win_ins_lines() caused the screen to be
	// cleared (only happens for the first window) or when screenclear()
	// was called directly above, "must_redraw" will have been set to
	// NOT_VALID, need to reset it here to avoid redrawing twice.
	if (screen_cleared == TRUE)
	    must_redraw = 0;
    }
    else
    {
	// Not VALID or INVERTED: redraw all lines.
	mid_start = 0;
	mid_end = wp->w_height;
    }

    if (type == SOME_VALID)
    {
	// SOME_VALID: redraw all lines.
	mid_start = 0;
	mid_end = wp->w_height;
	type = NOT_VALID;
    }

    // check if we are updating or removing the inverted part
    if ((VIsual_active && buf == curwin->w_buffer)
	    || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID))
    {
	linenr_T    from, to;

	if (VIsual_active)
	{
	    if (VIsual_active
		    && (VIsual_mode != wp->w_old_visual_mode
			|| type == INVERTED_ALL))
	    {
		// If the type of Visual selection changed, redraw the whole
		// selection.  Also when the ownership of the X selection is
		// gained or lost.
		if (curwin->w_cursor.lnum < VIsual.lnum)
		{
		    from = curwin->w_cursor.lnum;
		    to = VIsual.lnum;
		}
		else
		{
		    from = VIsual.lnum;
		    to = curwin->w_cursor.lnum;
		}
		// redraw more when the cursor moved as well
		if (wp->w_old_cursor_lnum < from)
		    from = wp->w_old_cursor_lnum;
		if (wp->w_old_cursor_lnum > to)
		    to = wp->w_old_cursor_lnum;
		if (wp->w_old_visual_lnum < from)
		    from = wp->w_old_visual_lnum;
		if (wp->w_old_visual_lnum > to)
		    to = wp->w_old_visual_lnum;
	    }
	    else
	    {
		// Find the line numbers that need to be updated: The lines
		// between the old cursor position and the current cursor
		// position.  Also check if the Visual position changed.
		if (curwin->w_cursor.lnum < wp->w_old_cursor_lnum)
		{
		    from = curwin->w_cursor.lnum;
		    to = wp->w_old_cursor_lnum;
		}
		else
		{
		    from = wp->w_old_cursor_lnum;
		    to = curwin->w_cursor.lnum;
		    if (from == 0)	// Visual mode just started
			from = to;
		}

		if (VIsual.lnum != wp->w_old_visual_lnum
					|| VIsual.col != wp->w_old_visual_col)
		{
		    if (wp->w_old_visual_lnum < from
						&& wp->w_old_visual_lnum != 0)
			from = wp->w_old_visual_lnum;
		    if (wp->w_old_visual_lnum > to)
			to = wp->w_old_visual_lnum;
		    if (VIsual.lnum < from)
			from = VIsual.lnum;
		    if (VIsual.lnum > to)
			to = VIsual.lnum;
		}
	    }

	    // If in block mode and changed column or curwin->w_curswant:
	    // update all lines.
	    // First compute the actual start and end column.
	    if (VIsual_mode == Ctrl_V)
	    {
		colnr_T	    fromc, toc;
#if defined(FEAT_LINEBREAK)
		int	    save_ve_flags = curwin->w_ve_flags;

		if (curwin->w_p_lbr)
		    curwin->w_ve_flags = VE_ALL;
#endif
		getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc);
		++toc;
#if defined(FEAT_LINEBREAK)
		curwin->w_ve_flags = save_ve_flags;
#endif
		// Highlight to the end of the line, unless 'virtualedit' has
		// "block".
		if (curwin->w_curswant == MAXCOL)
		{
		    if (get_ve_flags() & VE_BLOCK)
		    {
			pos_T	    pos;
			int	    cursor_above =
					   curwin->w_cursor.lnum < VIsual.lnum;

			// Need to find the longest line.
			toc = 0;
			pos.coladd = 0;
			for (pos.lnum = curwin->w_cursor.lnum; cursor_above
					? pos.lnum <= VIsual.lnum
					: pos.lnum >= VIsual.lnum;
					     pos.lnum += cursor_above ? 1 : -1)
			{
			    colnr_T t;

			    pos.col = (int)STRLEN(ml_get_buf(wp->w_buffer,
							     pos.lnum, FALSE));
			    getvvcol(wp, &pos, NULL, NULL, &t);
			    if (toc < t)
				toc = t;
			}
			++toc;
		    }
		    else
			toc = MAXCOL;
		}

		if (fromc != wp->w_old_cursor_fcol
			|| toc != wp->w_old_cursor_lcol)
		{
		    if (from > VIsual.lnum)
			from = VIsual.lnum;
		    if (to < VIsual.lnum)
			to = VIsual.lnum;
		}
		wp->w_old_cursor_fcol = fromc;
		wp->w_old_cursor_lcol = toc;
	    }
	}
	else
	{
	    // Use the line numbers of the old Visual area.
	    if (wp->w_old_cursor_lnum < wp->w_old_visual_lnum)
	    {
		from = wp->w_old_cursor_lnum;
		to = wp->w_old_visual_lnum;
	    }
	    else
	    {
		from = wp->w_old_visual_lnum;
		to = wp->w_old_cursor_lnum;
	    }
	}

	// There is no need to update lines above the top of the window.
	if (from < wp->w_topline)
	    from = wp->w_topline;

	// If we know the value of w_botline, use it to restrict the update to
	// the lines that are visible in the window.
	if (wp->w_valid & VALID_BOTLINE)
	{
	    if (from >= wp->w_botline)
		from = wp->w_botline - 1;
	    if (to >= wp->w_botline)
		to = wp->w_botline - 1;
	}

	// Find the minimal part to be updated.
	// Watch out for scrolling that made entries in w_lines[] invalid.
	// E.g., CTRL-U makes the first half of w_lines[] invalid and sets
	// top_end; need to redraw from top_end to the "to" line.
	// A middle mouse click with a Visual selection may change the text
	// above the Visual area and reset wl_valid, do count these for
	// mid_end (in srow).
	if (mid_start > 0)
	{
	    lnum = wp->w_topline;
	    idx = 0;
	    srow = 0;
	    if (scrolled_down)
		mid_start = top_end;
	    else
		mid_start = 0;
	    while (lnum < from && idx < wp->w_lines_valid)	// find start
	    {
		if (wp->w_lines[idx].wl_valid)
		    mid_start += wp->w_lines[idx].wl_size;
		else if (!scrolled_down)
		    srow += wp->w_lines[idx].wl_size;
		++idx;
# ifdef FEAT_FOLDING
		if (idx < wp->w_lines_valid && wp->w_lines[idx].wl_valid)
		    lnum = wp->w_lines[idx].wl_lnum;
		else
# endif
		    ++lnum;
	    }
	    srow += mid_start;
	    mid_end = wp->w_height;
	    for ( ; idx < wp->w_lines_valid; ++idx)		// find end
	    {
		if (wp->w_lines[idx].wl_valid
			&& wp->w_lines[idx].wl_lnum >= to + 1)
		{
		    // Only update until first row of this line
		    mid_end = srow;
		    break;
		}
		srow += wp->w_lines[idx].wl_size;
	    }
	}
    }

    if (VIsual_active && buf == curwin->w_buffer)
    {
	wp->w_old_visual_mode = VIsual_mode;
	wp->w_old_cursor_lnum = curwin->w_cursor.lnum;
	wp->w_old_visual_lnum = VIsual.lnum;
	wp->w_old_visual_col = VIsual.col;
	wp->w_old_curswant = curwin->w_curswant;
    }
    else
    {
	wp->w_old_visual_mode = 0;
	wp->w_old_cursor_lnum = 0;
	wp->w_old_visual_lnum = 0;
	wp->w_old_visual_col = 0;
    }

#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA)
    // reset got_int, otherwise regexp won't work
    save_got_int = got_int;
    got_int = 0;
#endif
#ifdef SYN_TIME_LIMIT
    // Set the time limit to 'redrawtime'.
    profile_setlimit(p_rdt, &syntax_tm);
    syn_set_timeout(&syntax_tm);
#endif
#ifdef FEAT_FOLDING
    win_foldinfo.fi_level = 0;
#endif

#ifdef FEAT_MENU
    // Draw the window toolbar, if there is one.
    // TODO: only when needed.
    if (winbar_height(wp) > 0)
	redraw_win_toolbar(wp);
#endif

    // Update all the window rows.
    idx = 0;		// first entry in w_lines[].wl_size
    row = 0;
    srow = 0;
    lnum = wp->w_topline;	// first line shown in window
    for (;;)
    {
	// stop updating when reached the end of the window (check for _past_
	// the end of the window is at the end of the loop)
	if (row == wp->w_height)
	{
	    didline = TRUE;
	    break;
	}

	// stop updating when hit the end of the file
	if (lnum > buf->b_ml.ml_line_count)
	{
	    eof = TRUE;
	    break;
	}

	// Remember the starting row of the line that is going to be dealt
	// with.  It is used further down when the line doesn't fit.
	srow = row;

	// Update a line when it is in an area that needs updating, when it
	// has changes or w_lines[idx] is invalid.
	// "bot_start" may be halfway a wrapped line after using
	// win_del_lines(), check if the current line includes it.
	// When syntax folding is being used, the saved syntax states will
	// already have been updated, we can't see where the syntax state is
	// the same again, just update until the end of the window.
	if (row < top_end
		|| (row >= mid_start && row < mid_end)
#ifdef FEAT_SEARCH_EXTRA
		|| top_to_mod
#endif
		|| idx >= wp->w_lines_valid
		|| (row + wp->w_lines[idx].wl_size > bot_start)
		|| (mod_top != 0
		    && (lnum == mod_top
			|| (lnum >= mod_top
			    && (lnum < mod_bot
#ifdef FEAT_SYN_HL
				|| did_update == DID_FOLD
				|| (did_update == DID_LINE
				    && syntax_present(wp)
				    && (
# ifdef FEAT_FOLDING
					(foldmethodIsSyntax(wp)
						      && hasAnyFolding(wp)) ||
# endif
					syntax_check_changed(lnum)))
#endif
#ifdef FEAT_SEARCH_EXTRA
				// match in fixed position might need redraw
				// if lines were inserted or deleted
				|| (wp->w_match_head != NULL
						    && buf->b_mod_xlines != 0)
#endif
				))))
#ifdef FEAT_SYN_HL
		|| (wp->w_p_cul && (lnum == wp->w_cursor.lnum
					     || lnum == wp->w_last_cursorline))
#endif
				)
	{
#ifdef FEAT_SEARCH_EXTRA
	    if (lnum == mod_top)
		top_to_mod = FALSE;
#endif

	    // When at start of changed lines: May scroll following lines
	    // up or down to minimize redrawing.
	    // Don't do this when the change continues until the end.
	    // Don't scroll when dollar_vcol >= 0, keep the "$".
	    // Don't scroll when redrawing the top, scrolled already above.
	    if (lnum == mod_top
		    && mod_bot != MAXLNUM
		    && !(dollar_vcol >= 0 && mod_bot == mod_top + 1)
		    && row >= top_end)
	    {
		int		old_rows = 0;
		int		new_rows = 0;
		int		xtra_rows;
		linenr_T	l;

		// Count the old number of window rows, using w_lines[], which
		// should still contain the sizes for the lines as they are
		// currently displayed.
		for (i = idx; i < wp->w_lines_valid; ++i)
		{
		    // Only valid lines have a meaningful wl_lnum.  Invalid
		    // lines are part of the changed area.
		    if (wp->w_lines[i].wl_valid
			    && wp->w_lines[i].wl_lnum == mod_bot)
			break;
		    old_rows += wp->w_lines[i].wl_size;
#ifdef FEAT_FOLDING
		    if (wp->w_lines[i].wl_valid
			    && wp->w_lines[i].wl_lastlnum + 1 == mod_bot)
		    {
			// Must have found the last valid entry above mod_bot.
			// Add following invalid entries.
			++i;
			while (i < wp->w_lines_valid
						  && !wp->w_lines[i].wl_valid)
			    old_rows += wp->w_lines[i++].wl_size;
			break;
		    }
#endif
		}

		if (i >= wp->w_lines_valid)
		{
		    // We can't find a valid line below the changed lines,
		    // need to redraw until the end of the window.
		    // Inserting/deleting lines has no use.
		    bot_start = 0;
		}
		else
		{
		    // Able to count old number of rows: Count new window
		    // rows, and may insert/delete lines
		    j = idx;
		    for (l = lnum; l < mod_bot; ++l)
		    {
#ifdef FEAT_FOLDING
			if (hasFoldingWin(wp, l, NULL, &l, TRUE, NULL))
			    ++new_rows;
			else
#endif
#ifdef FEAT_DIFF
			    if (l == wp->w_topline)
			    new_rows += plines_win_nofill(wp, l, TRUE)
							      + wp->w_topfill;
			else
#endif
			    new_rows += plines_win(wp, l, TRUE);
			++j;
			if (new_rows > wp->w_height - row - 2)
			{
			    // it's getting too much, must redraw the rest
			    new_rows = 9999;
			    break;
			}
		    }
		    xtra_rows = new_rows - old_rows;
		    if (xtra_rows < 0)
		    {
			// May scroll text up.  If there is not enough
			// remaining text or scrolling fails, must redraw the
			// rest.  If scrolling works, must redraw the text
			// below the scrolled text.
			if (row - xtra_rows >= wp->w_height - 2)
			    mod_bot = MAXLNUM;
			else
			{
			    check_for_delay(FALSE);
			    if (win_del_lines(wp, row,
					  -xtra_rows, FALSE, FALSE, 0) == FAIL)
				mod_bot = MAXLNUM;
			    else
				bot_start = wp->w_height + xtra_rows;
			}
		    }
		    else if (xtra_rows > 0)
		    {
			// May scroll text down.  If there is not enough
			// remaining text of scrolling fails, must redraw the
			// rest.
			if (row + xtra_rows >= wp->w_height - 2)
			    mod_bot = MAXLNUM;
			else
			{
			    check_for_delay(FALSE);
			    if (win_ins_lines(wp, row + old_rows,
					     xtra_rows, FALSE, FALSE) == FAIL)
				mod_bot = MAXLNUM;
			    else if (top_end > row + old_rows)
				// Scrolled the part at the top that requires
				// updating down.
				top_end += xtra_rows;
			}
		    }

		    // When not updating the rest, may need to move w_lines[]
		    // entries.
		    if (mod_bot != MAXLNUM && i != j)
		    {
			if (j < i)
			{
			    int x = row + new_rows;

			    // move entries in w_lines[] upwards
			    for (;;)
			    {
				// stop at last valid entry in w_lines[]
				if (i >= wp->w_lines_valid)
				{
				    wp->w_lines_valid = j;
				    break;
				}
				wp->w_lines[j] = wp->w_lines[i];
				// stop at a line that won't fit
				if (x + (int)wp->w_lines[j].wl_size
							   > wp->w_height)
				{
				    wp->w_lines_valid = j + 1;
				    break;
				}
				x += wp->w_lines[j++].wl_size;
				++i;
			    }
			    if (bot_start > x)
				bot_start = x;
			}
			else // j > i
			{
			    // move entries in w_lines[] downwards
			    j -= i;
			    wp->w_lines_valid += j;
			    if (wp->w_lines_valid > wp->w_height)
				wp->w_lines_valid = wp->w_height;
			    for (i = wp->w_lines_valid; i - j >= idx; --i)
				wp->w_lines[i] = wp->w_lines[i - j];

			    // The w_lines[] entries for inserted lines are
			    // now invalid, but wl_size may be used above.
			    // Reset to zero.
			    while (i >= idx)
			    {
				wp->w_lines[i].wl_size = 0;
				wp->w_lines[i--].wl_valid = FALSE;
			    }
			}
		    }
		}
	    }

#ifdef FEAT_FOLDING
	    // When lines are folded, display one line for all of them.
	    // Otherwise, display normally (can be several display lines when
	    // 'wrap' is on).
	    fold_count = foldedCount(wp, lnum, &win_foldinfo);
	    if (fold_count != 0)
	    {
		fold_line(wp, fold_count, &win_foldinfo, lnum, row);
		++row;
		--fold_count;
		wp->w_lines[idx].wl_folded = TRUE;
		wp->w_lines[idx].wl_lastlnum = lnum + fold_count;
# ifdef FEAT_SYN_HL
		did_update = DID_FOLD;
# endif
	    }
	    else
#endif
	    if (idx < wp->w_lines_valid
		    && wp->w_lines[idx].wl_valid
		    && wp->w_lines[idx].wl_lnum == lnum
		    && lnum > wp->w_topline
		    && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
		    && !WIN_IS_POPUP(wp)
		    && srow + wp->w_lines[idx].wl_size > wp->w_height
#ifdef FEAT_DIFF
		    && diff_check_fill(wp, lnum) == 0
#endif
		    )
	    {
		// This line is not going to fit.  Don't draw anything here,
		// will draw "@  " lines below.
		row = wp->w_height + 1;
	    }
	    else
	    {
#ifdef FEAT_SEARCH_EXTRA
		prepare_search_hl(wp, &screen_search_hl, lnum);
#endif
#ifdef FEAT_SYN_HL
		// Let the syntax stuff know we skipped a few lines.
		if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum
						       && syntax_present(wp))
		    syntax_end_parsing(syntax_last_parsed + 1);
#endif

		// Display one line.
		row = win_line(wp, lnum, srow, wp->w_height,
							  mod_top == 0, FALSE);

#ifdef FEAT_FOLDING
		wp->w_lines[idx].wl_folded = FALSE;
		wp->w_lines[idx].wl_lastlnum = lnum;
#endif
#ifdef FEAT_SYN_HL
		did_update = DID_LINE;
		syntax_last_parsed = lnum;
#endif
	    }

	    wp->w_lines[idx].wl_lnum = lnum;
	    wp->w_lines[idx].wl_valid = TRUE;

	    // Past end of the window or end of the screen. Note that after
	    // resizing wp->w_height may be end up too big. That's a problem
	    // elsewhere, but prevent a crash here.
	    if (row > wp->w_height || row + wp->w_winrow >= Rows)
	    {
		// we may need the size of that too long line later on
		if (dollar_vcol == -1)
		    wp->w_lines[idx].wl_size = plines_win(wp, lnum, TRUE);
		++idx;
		break;
	    }
	    if (dollar_vcol == -1)
		wp->w_lines[idx].wl_size = row - srow;
	    ++idx;
#ifdef FEAT_FOLDING
	    lnum += fold_count + 1;
#else
	    ++lnum;
#endif
	}
	else
	{
	    if (wp->w_p_rnu)
	    {
#ifdef FEAT_FOLDING
		// 'relativenumber' set: The text doesn't need to be drawn, but
		// the number column nearly always does.
		fold_count = foldedCount(wp, lnum, &win_foldinfo);
		if (fold_count != 0)
		    fold_line(wp, fold_count, &win_foldinfo, lnum, row);
		else
#endif
		    (void)win_line(wp, lnum, srow, wp->w_height, TRUE, TRUE);
	    }

	    // This line does not need to be drawn, advance to the next one.
	    row += wp->w_lines[idx++].wl_size;
	    if (row > wp->w_height)	// past end of screen
		break;
#ifdef FEAT_FOLDING
	    lnum = wp->w_lines[idx - 1].wl_lastlnum + 1;
#else
	    ++lnum;
#endif
#ifdef FEAT_SYN_HL
	    did_update = DID_NONE;
#endif
	}

	if (lnum > buf->b_ml.ml_line_count)
	{
	    eof = TRUE;
	    break;
	}
    }

    // End of loop over all window lines.

#ifdef FEAT_VTP
    // Rewrite the character at the end of the screen line.
    // See the version that was fixed.
    if (use_vtp() && get_conpty_fix_type() < 1)
    {
	int i;

	for (i = 0; i < Rows; ++i)
	    if (enc_utf8)
		if ((*mb_off2cells)(LineOffset[i] + Columns - 2,
					   LineOffset[i] + screen_Columns) > 1)
		    screen_draw_rectangle(i, Columns - 2, 1, 2, FALSE);
		else
		    screen_draw_rectangle(i, Columns - 1, 1, 1, FALSE);
	    else
		screen_char(LineOffset[i] + Columns - 1, i, Columns - 1);
    }
#endif

    if (idx > wp->w_lines_valid)
	wp->w_lines_valid = idx;

#ifdef FEAT_SYN_HL
    // Let the syntax stuff know we stop parsing here.
    if (syntax_last_parsed != 0 && syntax_present(wp))
	syntax_end_parsing(syntax_last_parsed + 1);
#endif

    // If we didn't hit the end of the file, and we didn't finish the last
    // line we were working on, then the line didn't fit.
    wp->w_empty_rows = 0;
#ifdef FEAT_DIFF
    wp->w_filler_rows = 0;
#endif
    if (!eof && !didline)
    {
	if (lnum == wp->w_topline)
	{
	    // Single line that does not fit!
	    // Don't overwrite it, it can be edited.
	    wp->w_botline = lnum + 1;
	}
#ifdef FEAT_DIFF
	else if (diff_check_fill(wp, lnum) >= wp->w_height - srow)
	{
	    // Window ends in filler lines.
	    wp->w_botline = lnum;
	    wp->w_filler_rows = wp->w_height - srow;
	}
#endif
#ifdef FEAT_PROP_POPUP
	else if (WIN_IS_POPUP(wp))
	{
	    // popup line that doesn't fit is left as-is
	    wp->w_botline = lnum;
	}
#endif
	else if (dy_flags & DY_TRUNCATE)	// 'display' has "truncate"
	{
	    int scr_row = W_WINROW(wp) + wp->w_height - 1;

	    // Last line isn't finished: Display "@@@" in the last screen line.
	    screen_puts_len((char_u *)"@@", 2, scr_row, wp->w_wincol,
							      HL_ATTR(HLF_AT));
	    screen_fill(scr_row, scr_row + 1,
		    (int)wp->w_wincol + 2, (int)W_ENDCOL(wp),
		    '@', ' ', HL_ATTR(HLF_AT));
	    set_empty_rows(wp, srow);
	    wp->w_botline = lnum;
	}
	else if (dy_flags & DY_LASTLINE)	// 'display' has "lastline"
	{
	    // Last line isn't finished: Display "@@@" at the end.
	    screen_fill(W_WINROW(wp) + wp->w_height - 1,
		    W_WINROW(wp) + wp->w_height,
		    (int)W_ENDCOL(wp) - 3, (int)W_ENDCOL(wp),
		    '@', '@', HL_ATTR(HLF_AT));
	    set_empty_rows(wp, srow);
	    wp->w_botline = lnum;
	}
	else
	{
	    win_draw_end(wp, '@', ' ', TRUE, srow, wp->w_height, HLF_AT);
	    wp->w_botline = lnum;
	}
    }
    else
    {
	draw_vsep_win(wp, row);
	if (eof)		// we hit the end of the file
	{
	    wp->w_botline = buf->b_ml.ml_line_count + 1;
#ifdef FEAT_DIFF
	    j = diff_check_fill(wp, wp->w_botline);
	    if (j > 0 && !wp->w_botfill)
	    {
		// Display filler lines at the end of the file.
		if (char2cells(fill_diff) > 1)
		    i = '-';
		else
		    i = fill_diff;
		if (row + j > wp->w_height)
		    j = wp->w_height - row;
		win_draw_end(wp, i, i, TRUE, row, row + (int)j, HLF_DED);
		row += j;
	    }
#endif
	}
	else if (dollar_vcol == -1)
	    wp->w_botline = lnum;

	// Make sure the rest of the screen is blank
	// write the 'fill_eob' character to rows that aren't part of the file
	if (WIN_IS_POPUP(wp))
	    win_draw_end(wp, ' ', ' ', FALSE, row, wp->w_height, HLF_AT);
	else
	    win_draw_end(wp, fill_eob, ' ', FALSE, row, wp->w_height, HLF_EOB);
    }

#ifdef SYN_TIME_LIMIT
    syn_set_timeout(NULL);
#endif

    // Reset the type of redrawing required, the window has been updated.
    wp->w_redr_type = 0;
#ifdef FEAT_DIFF
    wp->w_old_topfill = wp->w_topfill;
    wp->w_old_botfill = wp->w_botfill;
#endif

    if (dollar_vcol == -1)
    {
	// There is a trick with w_botline.  If we invalidate it on each
	// change that might modify it, this will cause a lot of expensive
	// calls to plines() in update_topline() each time.  Therefore the
	// value of w_botline is often approximated, and this value is used to
	// compute the value of w_topline.  If the value of w_botline was
	// wrong, check that the value of w_topline is correct (cursor is on
	// the visible part of the text).  If it's not, we need to redraw
	// again.  Mostly this just means scrolling up a few lines, so it
	// doesn't look too bad.  Only do this for the current window (where
	// changes are relevant).
	wp->w_valid |= VALID_BOTLINE;
	if (wp == curwin && wp->w_botline != old_botline && !recursive)
	{
	    win_T	*wwp;
#if defined(FEAT_CONCEAL)
	    linenr_T	old_topline = wp->w_topline;
	    int		new_wcol = wp->w_wcol;
#endif
	    recursive = TRUE;
	    curwin->w_valid &= ~VALID_TOPLINE;
	    update_topline();	// may invalidate w_botline again

#if defined(FEAT_CONCEAL)
	    if (old_wcol != new_wcol && (wp->w_valid & (VALID_WCOL|VALID_WROW))
						    != (VALID_WCOL|VALID_WROW))
	    {
		// A win_line() call applied a fix to screen cursor column to
		// accommodate concealment of cursor line, but in this call to
		// update_topline() the cursor's row or column got invalidated.
		// If they are left invalid, setcursor() will recompute them
		// but there won't be any further win_line() call to re-fix the
		// column and the cursor will end up misplaced.  So we call
		// cursor validation now and reapply the fix again (or call
		// win_line() to do it for us).
		validate_cursor();
		if (wp->w_wcol == old_wcol && wp->w_wrow == old_wrow
					       && old_topline == wp->w_topline)
		    wp->w_wcol = new_wcol;
		else
		    redrawWinline(wp, wp->w_cursor.lnum);
	    }
#endif
	    // New redraw either due to updated topline or due to wcol fix.
	    if (wp->w_redr_type != 0)
	    {
		// Don't update for changes in buffer again.
		i = curbuf->b_mod_set;
		curbuf->b_mod_set = FALSE;
		j = curbuf->b_mod_xlines;
		curbuf->b_mod_xlines = 0;
		win_update(curwin);
		curbuf->b_mod_set = i;
		curbuf->b_mod_xlines = j;
	    }
	    // Other windows might have w_redr_type raised in update_topline().
	    must_redraw = 0;
	    FOR_ALL_WINDOWS(wwp)
		if (wwp->w_redr_type > must_redraw)
		    must_redraw = wwp->w_redr_type;
	    recursive = FALSE;
	}
    }

#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA)
    // restore got_int, unless CTRL-C was hit while redrawing
    if (!got_int)
	got_int = save_got_int;
#endif
}


win_redr_ruler(win_T *wp, int always, int ignore_pum)
{
#define RULER_BUF_LEN 70
    char_u	buffer[RULER_BUF_LEN];
    int		row;
    int		fillchar;
    int		attr;
    int		empty_line = FALSE;
    colnr_T	virtcol;
    int		i;
    size_t	len;
    int		o;
    int		this_ru_col;
    int		off = 0;
    int		width;

    // If 'ruler' off or redrawing disabled, don't do anything
    if (!p_ru)
	return;

    /*
     * Check if cursor.lnum is valid, since win_redr_ruler() may be called
     * after deleting lines, before cursor.lnum is corrected.
     */
    if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count)
	return;

    // Don't draw the ruler while doing insert-completion, it might overwrite
    // the (long) mode message.
    if (wp == lastwin && lastwin->w_status_height == 0)
	if (edit_submode != NULL)
	    return;
    // Don't draw the ruler when the popup menu is visible, it may overlap.
    // Except when the popup menu will be redrawn anyway.
    if (!ignore_pum && pum_visible())
	return;

#ifdef FEAT_STL_OPT
    if (*p_ruf)
    {
	int	called_emsg_before = called_emsg;

	win_redr_custom(wp, TRUE);
	if (called_emsg > called_emsg_before)
	    set_string_option_direct((char_u *)"rulerformat", -1,
					   (char_u *)"", OPT_FREE, SID_ERROR);
	return;
    }
#endif

    /*
     * Check if not in Insert mode and the line is empty (will show "0-1").
     */
    if (!(State & INSERT)
		&& *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE) == NUL)
	empty_line = TRUE;

    /*
     * Only draw the ruler when something changed.
     */
    validate_virtcol_win(wp);
    if (       redraw_cmdline
	    || always
	    || wp->w_cursor.lnum != wp->w_ru_cursor.lnum
	    || wp->w_cursor.col != wp->w_ru_cursor.col
	    || wp->w_virtcol != wp->w_ru_virtcol
	    || wp->w_cursor.coladd != wp->w_ru_cursor.coladd
	    || wp->w_topline != wp->w_ru_topline
	    || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count
#ifdef FEAT_DIFF
	    || wp->w_topfill != wp->w_ru_topfill
#endif
	    || empty_line != wp->w_ru_empty)
    {
	cursor_off();
	if (wp->w_status_height)
	{
	    row = statusline_row(wp);
	    fillchar = fillchar_status(&attr, wp);
	    off = wp->w_wincol;
	    width = wp->w_width;
	}
	else
	{
	    row = Rows - 1;
	    fillchar = ' ';
	    attr = 0;
	    width = Columns;
	    off = 0;
	}

	// In list mode virtcol needs to be recomputed
	virtcol = wp->w_virtcol;
	if (wp->w_p_list && wp->w_lcs_chars.tab1 == NUL)
	{
	    wp->w_p_list = FALSE;
	    getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
	    wp->w_p_list = TRUE;
	}

	/*
	 * Some sprintfs return the length, some return a pointer.
	 * To avoid portability problems we use strlen() here.
	 */
	vim_snprintf((char *)buffer, RULER_BUF_LEN, "%ld,",
		(wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
		    ? 0L
		    : (long)(wp->w_cursor.lnum));
	len = STRLEN(buffer);
	col_print(buffer + len, RULER_BUF_LEN - len,
			empty_line ? 0 : (int)wp->w_cursor.col + 1,
			(int)virtcol + 1);

	/*
	 * Add a "50%" if there is room for it.
	 * On the last line, don't print in the last column (scrolls the
	 * screen up on some terminals).
	 */
	i = (int)STRLEN(buffer);
	get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
	o = i + vim_strsize(buffer + i + 1);
	if (wp->w_status_height == 0)	// can't use last char of screen
	    ++o;
	this_ru_col = ru_col - (Columns - width);
	if (this_ru_col < 0)
	    this_ru_col = 0;
	// Never use more than half the window/screen width, leave the other
	// half for the filename.
	if (this_ru_col < (width + 1) / 2)
	    this_ru_col = (width + 1) / 2;
	if (this_ru_col + o < width)
	{
	    // need at least 3 chars left for get_rel_pos() + NUL
	    while (this_ru_col + o < width && RULER_BUF_LEN > i + 4)
	    {
		if (has_mbyte)
		    i += (*mb_char2bytes)(fillchar, buffer + i);
		else
		    buffer[i++] = fillchar;
		++o;
	    }
	    get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
	}
	// Truncate at window boundary.
	if (has_mbyte)
	{
	    o = 0;
	    for (i = 0; buffer[i] != NUL; i += (*mb_ptr2len)(buffer + i))
	    {
		o += (*mb_ptr2cells)(buffer + i);
		if (this_ru_col + o > width)
		{
		    buffer[i] = NUL;
		    break;
		}
	    }
	}
	else if (this_ru_col + (int)STRLEN(buffer) > width)
	    buffer[width - this_ru_col] = NUL;

	screen_puts(buffer, row, this_ru_col + off, attr);
	i = redraw_cmdline;
	screen_fill(row, row + 1,
		this_ru_col + off + (int)STRLEN(buffer),
		(int)(off + width),
		fillchar, fillchar, attr);
	// don't redraw the cmdline because of showing the ruler
	redraw_cmdline = i;
	wp->w_ru_cursor = wp->w_cursor;
	wp->w_ru_virtcol = wp->w_virtcol;
	wp->w_ru_empty = empty_line;
	wp->w_ru_topline = wp->w_topline;
	wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count;
#ifdef FEAT_DIFF
	wp->w_ru_topfill = wp->w_topfill;
#endif
    }
}

patch 8.2.3487: illegal memory access if buffer name is very long

Problem:    Illegal memory access if buffer name is very long.
Solution:   Make sure not to go over the end of the buffer.


status_redraw_all(void)
{
    win_T	*wp;

    FOR_ALL_WINDOWS(wp)
	if (wp->w_status_height)
	{
	    wp->w_redr_status = TRUE;
	    redraw_later(VALID);
	}
}


statusline_row(win_T *wp)
{
#if defined(FEAT_PROP_POPUP)
    // If the window is really zero height the winbar isn't displayed.
    if (wp->w_frame->fr_height == wp->w_status_height && !popup_is_popup(wp))
	return wp->w_winrow;
#endif
    return W_WINROW(wp) + wp->w_height;
}


redraw_all_later(int type)
{
    win_T	*wp;

    FOR_ALL_WINDOWS(wp)
	redraw_win_later(wp, type);
    // This may be needed when switching tabs.
    if (must_redraw < type)
	must_redraw = type;
}


updateWindow(win_T *wp)
{
    // return if already busy updating
    if (updating_screen)
	return;

    update_prepare();

#ifdef FEAT_CLIPBOARD
    // When Visual area changed, may have to update selection.
    if (clip_star.available && clip_isautosel_star())
	clip_update_selection(&clip_star);
    if (clip_plus.available && clip_isautosel_plus())
	clip_update_selection(&clip_plus);
#endif

    win_update(wp);

    // When the screen was cleared redraw the tab pages line.
    if (redraw_tabline)
	draw_tabline();

    if (wp->w_redr_status
# ifdef FEAT_CMDL_INFO
	    || p_ru
# endif
# ifdef FEAT_STL_OPT
	    || *p_stl != NUL || *wp->w_p_stl != NUL
# endif
	    )
	win_redr_status(wp, FALSE);

#ifdef FEAT_PROP_POPUP
    // Display popup windows on top of everything.
    update_popups(win_update);
#endif

    update_finish();
}


redraw_curbuf_later(int type)
{
    redraw_buf_later(curbuf, type);
}


redraw_custom_statusline(win_T *wp)
{
    static int	    entered = FALSE;
    int		    saved_did_emsg = did_emsg;

    // When called recursively return.  This can happen when the statusline
    // contains an expression that triggers a redraw.
    if (entered)
	return;
    entered = TRUE;

    did_emsg = FALSE;
    win_redr_custom(wp, FALSE);
    if (did_emsg)
    {
	// When there is an error disable the statusline, otherwise the
	// display is messed up with errors and a redraw triggers the problem
	// again and again.
	set_string_option_direct((char_u *)"statusline", -1,
		(char_u *)"", OPT_FREE | (*wp->w_p_stl != NUL
					? OPT_LOCAL : OPT_GLOBAL), SID_ERROR);
    }
    did_emsg |= saved_did_emsg;
    entered = FALSE;
}


redraw_win_later(
    win_T	*wp,
    int		type)
{
    if (!exiting && wp->w_redr_type < type)
    {
	wp->w_redr_type = type;
	if (type >= NOT_VALID)
	    wp->w_lines_valid = 0;
	if (must_redraw < type)	// must_redraw is the maximum of all windows
	    must_redraw = type;
    }
}


redraw_later_clear(void)
{
    redraw_all_later(CLEAR);
    reset_screen_attr();
}


status_redraw_curbuf(void)
{
    win_T	*wp;

    FOR_ALL_WINDOWS(wp)
	if (wp->w_status_height != 0 && wp->w_buffer == curbuf)
	{
	    wp->w_redr_status = TRUE;
	    redraw_later(VALID);
	}
}


redraw_after_callback(int call_update_screen)
{
    ++redrawing_for_callback;

    if (State == HITRETURN || State == ASKMORE)
	; // do nothing
    else if (State & CMDLINE)
    {
	// Don't redraw when in prompt_for_number().
	if (cmdline_row > 0)
	{
	    // Redrawing only works when the screen didn't scroll. Don't clear
	    // wildmenu entries.
	    if (msg_scrolled == 0
#ifdef FEAT_WILDMENU
		    && wild_menu_showing == 0
#endif
		    && call_update_screen)
		update_screen(0);

	    // Redraw in the same position, so that the user can continue
	    // editing the command.
	    redrawcmdline_ex(FALSE);
	}
    }
    else if (State & (NORMAL | INSERT | TERMINAL))
    {
	// keep the command line if possible
	update_screen(VALID_NO_UPDATE);
	setcursor();

	if (msg_scrolled == 0)
	{
	    // don't want a hit-enter prompt when something else is displayed
	    msg_didany = FALSE;
	    need_wait_return = FALSE;
	}
    }
    cursor_on();
#ifdef FEAT_GUI
    if (gui.in_use && !gui_mch_is_blink_off())
	// Don't update the cursor when it is blinking and off to avoid
	// flicker.
	out_flush_cursor(FALSE, FALSE);
    else
#endif
	out_flush();

    --redrawing_for_callback;
}


fold_line(
    win_T	*wp,
    long	fold_count,
    foldinfo_T	*foldinfo,
    linenr_T	lnum,
    int		row)
{
    // Max value of 'foldcolumn' is 12 and maximum number of bytes in a
    // multi-byte character is MAX_MCO.
    char_u	buf[MAX_MCO * 12 + 1];
    pos_T	*top, *bot;
    linenr_T	lnume = lnum + fold_count - 1;
    int		len;
    char_u	*text;
    int		fdc;
    int		col;
    int		txtcol;
    int		off = (int)(current_ScreenLine - ScreenLines);
    int		ri;

    // Build the fold line:
    // 1. Add the cmdwin_type for the command-line window
    // 2. Add the 'foldcolumn'
    // 3. Add the 'number' or 'relativenumber' column
    // 4. Compose the text
    // 5. Add the text
    // 6. set highlighting for the Visual area an other text
    col = 0;

    // 1. Add the cmdwin_type for the command-line window
    // Ignores 'rightleft', this window is never right-left.
#ifdef FEAT_CMDWIN
    if (cmdwin_type != 0 && wp == curwin)
    {
	ScreenLines[off] = cmdwin_type;
	ScreenAttrs[off] = HL_ATTR(HLF_AT);
	if (enc_utf8)
	    ScreenLinesUC[off] = 0;
	++col;
    }
#endif

#ifdef FEAT_RIGHTLEFT
# define RL_MEMSET(p, v, l) \
    do { \
	if (wp->w_p_rl) \
	    for (ri = 0; ri < l; ++ri) \
	       ScreenAttrs[off + (wp->w_width - (p) - (l)) + ri] = v; \
	 else \
	    for (ri = 0; ri < l; ++ri) \
	       ScreenAttrs[off + (p) + ri] = v; \
    } while (0)
#else
# define RL_MEMSET(p, v, l) \
    do { \
	for (ri = 0; ri < l; ++ri) \
	    ScreenAttrs[off + (p) + ri] = v; \
    } while (0)
#endif

    // 2. Add the 'foldcolumn'
    //    Reduce the width when there is not enough space.
    fdc = compute_foldcolumn(wp, col);
    if (fdc > 0)
    {
	char_u	*p;
	int	i;
	int	idx;

	fill_foldcolumn(buf, wp, TRUE, lnum);
	p = buf;
	for (i = 0; i < fdc; i++)
	{
	    int		ch;

	    if (has_mbyte)
		ch = mb_ptr2char_adv(&p);
	    else
		ch = *p++;
#ifdef FEAT_RIGHTLEFT
	    if (wp->w_p_rl)
		idx = off + wp->w_width - i - 1 - col;
	    else
#endif
		idx = off + col + i;
	    if (enc_utf8)
	    {
		if (ch >= 0x80)
		{
		    ScreenLinesUC[idx] = ch;
		    ScreenLinesC[0][idx] = 0;
		    ScreenLines[idx] = 0x80;
		}
		else
		{
		    ScreenLines[idx] = ch;
		    ScreenLinesUC[idx] = 0;
		}
	    }
	    else
		ScreenLines[idx] = ch;
	}

	RL_MEMSET(col, HL_ATTR(HLF_FC), fdc);
	col += fdc;
    }

    // Set all attributes of the 'number' or 'relativenumber' column and the
    // text
    RL_MEMSET(col, HL_ATTR(HLF_FL), wp->w_width - col);

#ifdef FEAT_SIGNS
    // If signs are being displayed, add two spaces.
    if (signcolumn_on(wp))
    {
	len = wp->w_width - col;
	if (len > 0)
	{
	    if (len > 2)
		len = 2;
# ifdef FEAT_RIGHTLEFT
	    if (wp->w_p_rl)
		// the line number isn't reversed
		copy_text_attr(off + wp->w_width - len - col,
					(char_u *)"  ", len, HL_ATTR(HLF_FL));
	    else
# endif
		copy_text_attr(off + col, (char_u *)"  ", len, HL_ATTR(HLF_FL));
	    col += len;
	}
    }
#endif

    // 3. Add the 'number' or 'relativenumber' column
    if (wp->w_p_nu || wp->w_p_rnu)
    {
	len = wp->w_width - col;
	if (len > 0)
	{
	    int	    w = number_width(wp);
	    long    num;
	    char    *fmt = "%*ld ";

	    if (len > w + 1)
		len = w + 1;

	    if (wp->w_p_nu && !wp->w_p_rnu)
		// 'number' + 'norelativenumber'
		num = (long)lnum;
	    else
	    {
		// 'relativenumber', don't use negative numbers
		num = labs((long)get_cursor_rel_lnum(wp, lnum));
		if (num == 0 && wp->w_p_nu && wp->w_p_rnu)
		{
		    // 'number' + 'relativenumber': cursor line shows absolute
		    // line number
		    num = lnum;
		    fmt = "%-*ld ";
		}
	    }

	    sprintf((char *)buf, fmt, w, num);
#ifdef FEAT_RIGHTLEFT
	    if (wp->w_p_rl)
		// the line number isn't reversed
		copy_text_attr(off + wp->w_width - len - col, buf, len,
							     HL_ATTR(HLF_FL));
	    else
#endif
		copy_text_attr(off + col, buf, len, HL_ATTR(HLF_FL));
	    col += len;
	}
    }

    // 4. Compose the folded-line string with 'foldtext', if set.
    text = get_foldtext(wp, lnum, lnume, foldinfo, buf);

    txtcol = col;	// remember where text starts

    // 5. move the text to current_ScreenLine.  Fill up with "fill_fold".
    //    Right-left text is put in columns 0 - number-col, normal text is put
    //    in columns number-col - window-width.
    col = text_to_screenline(wp, text, col);

    // Fill the rest of the line with the fold filler
#ifdef FEAT_RIGHTLEFT
    if (wp->w_p_rl)
	col -= txtcol;
#endif
    while (col < wp->w_width
#ifdef FEAT_RIGHTLEFT
		    - (wp->w_p_rl ? txtcol : 0)
#endif
	    )
    {
	if (enc_utf8)
	{
	    if (fill_fold >= 0x80)
	    {
		ScreenLinesUC[off + col] = fill_fold;
		ScreenLinesC[0][off + col] = 0;
		ScreenLines[off + col] = 0x80; // avoid storing zero
	    }
	    else
	    {
		ScreenLinesUC[off + col] = 0;
		ScreenLines[off + col] = fill_fold;
	    }
	    col++;
	}
	else
	    ScreenLines[off + col++] = fill_fold;
    }

    if (text != buf)
	vim_free(text);

    // 6. set highlighting for the Visual area an other text.
    // If all folded lines are in the Visual area, highlight the line.
    if (VIsual_active && wp->w_buffer == curwin->w_buffer)
    {
	if (LTOREQ_POS(curwin->w_cursor, VIsual))
	{
	    // Visual is after curwin->w_cursor
	    top = &curwin->w_cursor;
	    bot = &VIsual;
	}
	else
	{
	    // Visual is before curwin->w_cursor
	    top = &VIsual;
	    bot = &curwin->w_cursor;
	}
	if (lnum >= top->lnum
		&& lnume <= bot->lnum
		&& (VIsual_mode != 'v'
		    || ((lnum > top->lnum
			    || (lnum == top->lnum
				&& top->col == 0))
			&& (lnume < bot->lnum
			    || (lnume == bot->lnum
				&& (bot->col - (*p_sel == 'e'))
		>= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume, FALSE)))))))
	{
	    if (VIsual_mode == Ctrl_V)
	    {
		// Visual block mode: highlight the chars part of the block
		if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_width)
		{
		    if (wp->w_old_cursor_lcol != MAXCOL
			     && wp->w_old_cursor_lcol + txtcol
						       < (colnr_T)wp->w_width)
			len = wp->w_old_cursor_lcol;
		    else
			len = wp->w_width - txtcol;
		    RL_MEMSET(wp->w_old_cursor_fcol + txtcol, HL_ATTR(HLF_V),
					    len - (int)wp->w_old_cursor_fcol);
		}
	    }
	    else
	    {
		// Set all attributes of the text
		RL_MEMSET(txtcol, HL_ATTR(HLF_V), wp->w_width - txtcol);
	    }
	}
    }

#ifdef FEAT_SYN_HL
    // Show colorcolumn in the fold line, but let cursorcolumn override it.
    if (wp->w_p_cc_cols)
    {
	int i = 0;
	int j = wp->w_p_cc_cols[i];
	int old_txtcol = txtcol;

	while (j > -1)
	{
	    txtcol += j;
	    if (wp->w_p_wrap)
		txtcol -= wp->w_skipcol;
	    else
		txtcol -= wp->w_leftcol;
	    if (txtcol >= 0 && txtcol < wp->w_width)
		ScreenAttrs[off + txtcol] = hl_combine_attr(
				    ScreenAttrs[off + txtcol], HL_ATTR(HLF_MC));
	    txtcol = old_txtcol;
	    j = wp->w_p_cc_cols[++i];
	}
    }

    // Show 'cursorcolumn' in the fold line.
    if (wp->w_p_cuc)
    {
	txtcol += wp->w_virtcol;
	if (wp->w_p_wrap)
	    txtcol -= wp->w_skipcol;
	else
	    txtcol -= wp->w_leftcol;
	if (txtcol >= 0 && txtcol < wp->w_width)
	    ScreenAttrs[off + txtcol] = hl_combine_attr(
				 ScreenAttrs[off + txtcol], HL_ATTR(HLF_CUC));
    }
#endif

    screen_line(row + W_WINROW(wp), wp->w_wincol, (int)wp->w_width,
						     (int)wp->w_width, 0);

    // Update w_cline_height and w_cline_folded if the cursor line was
    // updated (saves a call to plines() later).
    if (wp == curwin
	    && lnum <= curwin->w_cursor.lnum
	    && lnume >= curwin->w_cursor.lnum)
    {
	curwin->w_cline_row = row;
	curwin->w_cline_height = 1;
	curwin->w_cline_folded = TRUE;
	curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
    }

# ifdef FEAT_CONCEAL
    // When the line was not folded w_wrow may have been set, recompute it.
    if (wp == curwin
	    && wp->w_cursor.lnum >= lnum
	    && wp->w_cursor.lnum <= lnume
	    && conceal_cursor_line(wp))
	curs_columns(TRUE);
# endif
}


update_finish(void)
{
    if (redraw_cmdline || redraw_mode)
	showmode();

# ifdef FEAT_SEARCH_EXTRA
    end_search_hl();
# endif

    after_updating_screen(TRUE);

# ifdef FEAT_GUI
    // Redraw the cursor and update the scrollbars when all screen updating is
    // done.
    if (gui.in_use)
    {
	out_flush_cursor(FALSE, FALSE);
	gui_update_scrollbars(FALSE);
    }
# endif
}


redraw_buf_and_status_later(buf_T *buf, int type)
{
    win_T	*wp;

#ifdef FEAT_WILDMENU
    if (wild_menu_showing != 0)
	// Don't redraw while the command line completion is displayed, it
	// would disappear.
	return;
#endif
    FOR_ALL_WINDOWS(wp)
    {
	if (wp->w_buffer == buf)
	{
	    redraw_win_later(wp, type);
	    wp->w_redr_status = TRUE;
	}
    }
}


static short ToS(unsigned char *puffer)
{
  return ((short)(puffer[0] | puffer[1] << 8));
}

input-bmp: Increase header buffer in some cases

Signed-off-by: Peter Lemenkov <lemenkov@gmail.com>


at_bitmap input_bmp_reader(gchar * filename, at_input_opts_type * opts, at_msg_func msg_func, gpointer msg_data, gpointer user_data)
{
  FILE *fd;
  unsigned char buffer[128];
  int ColormapSize, rowbytes, Maps;
  gboolean Grey = FALSE;
  unsigned char ColorMap[256][3];
  at_bitmap image = at_bitmap_init(0, 0, 0, 1);
  unsigned char *image_storage;
  at_exception_type exp = at_exception_new(msg_func, msg_data);
  char magick[2];
  Bitmap_Channel masks[4];

  fd = fopen(filename, "rb");

  if (!fd) {
    LOG("Can't open \"%s\"\n", filename);
    at_exception_fatal(&exp, "bmp: cannot open input file");
    goto cleanup;
  }

  /* It is a File. Now is it a Bitmap? Read the shortest possible header. */

  if (!ReadOK(fd, magick, 2) ||
	  !(!strncmp(magick, "BA", 2) ||
		  !strncmp(magick, "BM", 2) ||
		  !strncmp(magick, "IC", 2) ||
		  !strncmp(magick, "PT", 2) ||
		  !strncmp(magick, "CI", 2) ||
		  !strncmp(magick, "CP", 2)))
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  while (!strncmp(magick, "BA", 2))
  {
	  if (!ReadOK(fd, buffer, 12))
	  {
		  LOG("%s is not a valid BMP file", filename);
		  at_exception_fatal(&exp, "bmp: invalid input file");
		  goto cleanup;
	  }

	  if (!ReadOK(fd, magick, 2))
	  {
		  LOG("%s is not a valid BMP file", filename);
		  at_exception_fatal(&exp, "bmp: invalid input file");
		  goto cleanup;
	  }
  }

  if (!ReadOK(fd, buffer, 12))////
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  /* bring them to the right byteorder. Not too nice, but it should work */

  Bitmap_File_Head.bfSize = ToL(&buffer[0x00]);
  Bitmap_File_Head.zzHotX = ToS(&buffer[0x04]);
  Bitmap_File_Head.zzHotY = ToS(&buffer[0x06]);
  Bitmap_File_Head.bfOffs = ToL(&buffer[0x08]);

  if (!ReadOK(fd, buffer, 4))
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  Bitmap_File_Head.biSize = ToL(&buffer[0x00]);

  /* What kind of bitmap is it? */

  if (Bitmap_File_Head.biSize == 12) {  /* OS/2 1.x ? */
    if (!ReadOK(fd, buffer, 8)) {
      LOG("Error reading BMP file header\n");
      at_exception_fatal(&exp, "Error reading BMP file header");
      goto cleanup;
    }

    Bitmap_Head.biWidth = ToS(&buffer[0x00]); /* 12 */
    Bitmap_Head.biHeight = ToS(&buffer[0x02]);  /* 14 */
    Bitmap_Head.biPlanes = ToS(&buffer[0x04]);  /* 16 */
    Bitmap_Head.biBitCnt = ToS(&buffer[0x06]);  /* 18 */
    Bitmap_Head.biCompr = 0;
    Bitmap_Head.biSizeIm = 0;
    Bitmap_Head.biXPels = Bitmap_Head.biYPels = 0;
    Bitmap_Head.biClrUsed = 0;
    Bitmap_Head.biClrImp = 0;
    Bitmap_Head.masks[0] = 0;
    Bitmap_Head.masks[1] = 0;
    Bitmap_Head.masks[2] = 0;
    Bitmap_Head.masks[3] = 0;

    memset(masks, 0, sizeof(masks));
    Maps = 3;

  } else if (Bitmap_File_Head.biSize == 40) { /* Windows 3.x */
    if (!ReadOK(fd, buffer, 36))
    {
      LOG ("Error reading BMP file header\n");
      at_exception_fatal(&exp, "Error reading BMP file header");
      goto cleanup;
    }

    Bitmap_Head.biWidth = ToL(&buffer[0x00]); /* 12 */
    Bitmap_Head.biHeight = ToL(&buffer[0x04]);  /* 16 */
    Bitmap_Head.biPlanes = ToS(&buffer[0x08]);  /* 1A */
    Bitmap_Head.biBitCnt = ToS(&buffer[0x0A]);  /* 1C */
    Bitmap_Head.biCompr = ToL(&buffer[0x0C]); /* 1E */
    Bitmap_Head.biSizeIm = ToL(&buffer[0x10]);  /* 22 */
    Bitmap_Head.biXPels = ToL(&buffer[0x14]); /* 26 */
    Bitmap_Head.biYPels = ToL(&buffer[0x18]); /* 2A */
    Bitmap_Head.biClrUsed = ToL(&buffer[0x1C]); /* 2E */
    Bitmap_Head.biClrImp = ToL(&buffer[0x20]);  /* 32 */
    Bitmap_Head.masks[0] = 0;
    Bitmap_Head.masks[1] = 0;
    Bitmap_Head.masks[2] = 0;
    Bitmap_Head.masks[3] = 0;

    Maps = 4;
    memset(masks, 0, sizeof(masks));

    if (Bitmap_Head.biCompr == BI_BITFIELDS)
      {
	if (!ReadOK(fd, buffer, 3 * sizeof(unsigned long)))
	  {
	    LOG("Error reading BMP file header\n");
	    at_exception_fatal(&exp, "Error reading BMP file header");
	    goto cleanup;
	  }

	Bitmap_Head.masks[0] = ToL(&buffer[0x00]);
	Bitmap_Head.masks[1] = ToL(&buffer[0x04]);
	Bitmap_Head.masks[2] = ToL(&buffer[0x08]);

	ReadChannelMasks(&Bitmap_Head.masks[0], masks, 3);
      }
    else if (Bitmap_Head.biCompr == BI_RGB)
      {
	setMasksDefault(Bitmap_Head.biBitCnt, masks);
      }
    else if ((Bitmap_Head.biCompr != BI_RLE4) &&
	     (Bitmap_Head.biCompr != BI_RLE8))
      {
	/* BI_ALPHABITFIELDS, etc. */
	LOG("Unsupported compression in BMP file\n");
	at_exception_fatal(&exp, "Unsupported compression in BMP file");
	goto cleanup;
      }
  }
  else if (Bitmap_File_Head.biSize >= 56 &&
	   Bitmap_File_Head.biSize <= 64)
  {
    /* enhanced Windows format with bit masks */

    if (!ReadOK (fd, buffer, Bitmap_File_Head.biSize - 4))
    {

      LOG("Error reading BMP file header\n");
      at_exception_fatal(&exp, "Error reading BMP file header");
      goto cleanup;
    }

    Bitmap_Head.biWidth = ToL(&buffer[0x00]); /* 12 */
    Bitmap_Head.biHeight = ToL(&buffer[0x04]);  /* 16 */
    Bitmap_Head.biPlanes = ToS(&buffer[0x08]);  /* 1A */
    Bitmap_Head.biBitCnt = ToS(&buffer[0x0A]);  /* 1C */
    Bitmap_Head.biCompr = ToL(&buffer[0x0C]); /* 1E */
    Bitmap_Head.biSizeIm = ToL(&buffer[0x10]);  /* 22 */
    Bitmap_Head.biXPels = ToL(&buffer[0x14]); /* 26 */
    Bitmap_Head.biYPels = ToL(&buffer[0x18]); /* 2A */
    Bitmap_Head.biClrUsed = ToL(&buffer[0x1C]); /* 2E */
    Bitmap_Head.biClrImp = ToL(&buffer[0x20]);  /* 32 */
    Bitmap_Head.masks[0] = ToL(&buffer[0x24]);       /* 36 */
    Bitmap_Head.masks[1] = ToL(&buffer[0x28]);       /* 3A */
    Bitmap_Head.masks[2] = ToL(&buffer[0x2C]);       /* 3E */
    Bitmap_Head.masks[3] = ToL(&buffer[0x30]);       /* 42 */

    Maps = 4;
    ReadChannelMasks(&Bitmap_Head.masks[0], masks, 4);
  }
  else if (Bitmap_File_Head.biSize == 108 ||
           Bitmap_File_Head.biSize == 124)
  {
    /* BMP Version 4 or 5 */

    if (!ReadOK(fd, buffer, Bitmap_File_Head.biSize - 4))
    {
	    LOG("Error reading BMP file header\n");
	    at_exception_fatal(&exp, "Error reading BMP file header");
	    goto cleanup;
    }

    Bitmap_Head.biWidth = ToL(&buffer[0x00]);
    Bitmap_Head.biHeight = ToL(&buffer[0x04]);
    Bitmap_Head.biPlanes = ToS(&buffer[0x08]);
    Bitmap_Head.biBitCnt = ToS(&buffer[0x0A]);
    Bitmap_Head.biCompr = ToL(&buffer[0x0C]);
    Bitmap_Head.biSizeIm = ToL(&buffer[0x10]);
    Bitmap_Head.biXPels = ToL(&buffer[0x14]);
    Bitmap_Head.biYPels = ToL(&buffer[0x18]);
    Bitmap_Head.biClrUsed = ToL(&buffer[0x1C]);
    Bitmap_Head.biClrImp = ToL(&buffer[0x20]);
    Bitmap_Head.masks[0] = ToL(&buffer[0x24]);
    Bitmap_Head.masks[1] = ToL(&buffer[0x28]);
    Bitmap_Head.masks[2] = ToL(&buffer[0x2C]);
    Bitmap_Head.masks[3] = ToL(&buffer[0x30]);

    Maps = 4;

    if (Bitmap_Head.biCompr == BI_BITFIELDS)
    {
	    ReadChannelMasks(&Bitmap_Head.masks[0], masks, 4);
    }
    else if (Bitmap_Head.biCompr == BI_RGB)
    {
	    setMasksDefault(Bitmap_Head.biBitCnt, masks);
    }
  } else {
    LOG("Error reading BMP file header\n");
    at_exception_fatal(&exp, "Error reading BMP file header");
    goto cleanup;
  }

  /* Valid options 1, 4, 8, 16, 24, 32 */
  /* 16 is awful, we should probably shoot whoever invented it */

  switch (Bitmap_Head.biBitCnt)
  {
  case 1:
  case 2:
  case 4:
  case 8:
  case 16:
  case 24:
  case 32:
	  break;
  default:
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  /* There should be some colors used! */

  ColormapSize = (Bitmap_File_Head.bfOffs - Bitmap_File_Head.biSize - 14) / Maps;

  if ((Bitmap_Head.biClrUsed == 0) &&
      (Bitmap_Head.biBitCnt <= 8))
  {
	  ColormapSize = Bitmap_Head.biClrUsed = 1 << Bitmap_Head.biBitCnt;
  }

  if (ColormapSize > 256)
    ColormapSize = 256;

  /* Sanity checks */

  if (Bitmap_Head.biHeight == 0 ||
	  Bitmap_Head.biWidth == 0)
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  /* biHeight may be negative, but -2147483648 is dangerous because:
	 -2147483648 == -(-2147483648) */
  if (Bitmap_Head.biWidth < 0 ||
	  Bitmap_Head.biHeight == -2147483648)
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  if (Bitmap_Head.biPlanes != 1)
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  if (Bitmap_Head.biClrUsed > 256 &&
	  Bitmap_Head.biBitCnt <= 8)
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  /* protect against integer overflows caused by malicious BMPs */
  /* use divisions in comparisons to avoid type overflows */

  if (((unsigned long)Bitmap_Head.biWidth) > (unsigned int)0x7fffffff / Bitmap_Head.biBitCnt ||
	  ((unsigned long)Bitmap_Head.biWidth) > ((unsigned int)0x7fffffff /abs(Bitmap_Head.biHeight)) / 4)
  {
	  LOG("%s is not a valid BMP file", filename);
	  at_exception_fatal(&exp, "bmp: invalid input file");
	  goto cleanup;
  }

  /* Windows and OS/2 declare filler so that rows are a multiple of
   * word length (32 bits == 4 bytes)
   */

  unsigned long overflowTest = Bitmap_Head.biWidth * Bitmap_Head.biBitCnt;
  if (overflowTest / Bitmap_Head.biWidth != Bitmap_Head.biBitCnt) {
    LOG("Error reading BMP file header. Width is too large\n");
    at_exception_fatal(&exp, "Error reading BMP file header. Width is too large");
    goto cleanup;
  }

  rowbytes = ((Bitmap_Head.biWidth * Bitmap_Head.biBitCnt - 1) / 32) * 4 + 4;

#ifdef DEBUG
  printf("\nSize: %u, Colors: %u, Bits: %u, Width: %u, Height: %u, Comp: %u, Zeile: %u\n", Bitmap_File_Head.bfSize, Bitmap_Head.biClrUsed, Bitmap_Head.biBitCnt, Bitmap_Head.biWidth, Bitmap_Head.biHeight, Bitmap_Head.biCompr, rowbytes);
#endif

  if (Bitmap_Head.biBitCnt <= 8)
  {
#ifdef DEBUG
    printf("Colormap read\n");
#endif
	  /* Get the Colormap */
	  if (!ReadColorMap(fd, ColorMap, ColormapSize, Maps, &Grey, &exp))
		  goto cleanup;
  }

  fseek(fd, Bitmap_File_Head.bfOffs, SEEK_SET);

  /* Get the Image and return the ID or -1 on error */
  image_storage = ReadImage(fd,
	Bitmap_Head.biWidth, Bitmap_Head.biHeight,
	ColorMap,
        Bitmap_Head.biClrUsed,
	Bitmap_Head.biBitCnt, Bitmap_Head.biCompr, rowbytes,
        Grey,
	masks,
	&exp);

  image = at_bitmap_init(image_storage, (unsigned short)Bitmap_Head.biWidth, (unsigned short)Bitmap_Head.biHeight, Grey ? 1 : 3);
cleanup:
  fclose(fd);
  return (image);
}


static long ToL(unsigned char *puffer)
{
  return (puffer[0] | puffer[1] << 8 | puffer[2] << 16 | puffer[3] << 24);
}


setMasksDefault (unsigned short        biBitCnt,
                 Bitmap_Channel *masks)
{
  switch (biBitCnt)
    {
    case 32:
      masks[0].mask      = 0x00ff0000;
      masks[0].shiftin   = 16;
      masks[0].max_value = (float)255.0;
      masks[1].mask      = 0x0000ff00;
      masks[1].shiftin   = 8;
      masks[1].max_value = (float)255.0;
      masks[2].mask      = 0x000000ff;
      masks[2].shiftin   = 0;
      masks[2].max_value = (float)255.0;
      masks[3].mask      = 0x00000000;
      masks[3].shiftin   = 0;
      masks[3].max_value = (float)0.0;
      break;

    case 24:
      masks[0].mask      = 0xff0000;
      masks[0].shiftin   = 16;
      masks[0].max_value = (float)255.0;
      masks[1].mask      = 0x00ff00;
      masks[1].shiftin   = 8;
      masks[1].max_value = (float)255.0;
      masks[2].mask      = 0x0000ff;
      masks[2].shiftin   = 0;
      masks[2].max_value = (float)255.0;
      masks[3].mask      = 0x0;
      masks[3].shiftin   = 0;
      masks[3].max_value = (float)0.0;
      break;

    case 16:
      masks[0].mask      = 0x7c00;
      masks[0].shiftin   = 10;
      masks[0].max_value = (float)31.0;
      masks[1].mask      = 0x03e0;
      masks[1].shiftin   = 5;
      masks[1].max_value = (float)31.0;
      masks[2].mask      = 0x001f;
      masks[2].shiftin   = 0;
      masks[2].max_value = (float)31.0;
      masks[3].mask      = 0x0;
      masks[3].shiftin   = 0;
      masks[3].max_value = (float)0.0;
      break;

    default:
      break;
    }
}


static unsigned char *ReadImage(FILE * fd, int width, int height,
	unsigned char cmap[256][3],
	int ncols,
	int bpp, int compression, int rowbytes,
	gboolean    Grey,
	const Bitmap_Channel * masks,
	at_exception_type * exp)
{
  unsigned char v, n;
  int xpos = 0;
  int ypos = 0;
  unsigned char * image;
  unsigned char *dest, *temp, *row_buf;
  long rowstride, channels;
  unsigned short rgb;
  int i, i_max, j;
  int total_bytes_read;
  unsigned int px32;

  if (!(compression == BI_RGB ||
	  (bpp == 8 && compression == BI_RLE8) ||
	  (bpp == 4 && compression == BI_RLE4) ||
	  (bpp == 16 && compression == BI_BITFIELDS) ||
	  (bpp == 32 && compression == BI_BITFIELDS)))
  {
	  LOG("Unrecognized or invalid BMP compression format.\n");
	  at_exception_fatal(exp, "Unrecognized or invalid BMP compression format.");
	  return NULL;
  }

  if (bpp >= 16) {              /* color image */
    XMALLOC(image, width * height * 3 * sizeof(unsigned char));
    if (masks[3].mask != 0)
    {
      channels = 4;
    }
    else
    {
      channels = 3;
    }
  }
  else if (Grey) /* Grey image */
  {
    XMALLOC(image, width * height * 1 * sizeof(unsigned char));
    channels = 1;
  } else {                      /* indexed image */

    XMALLOC(image, width * height * 1 * sizeof(unsigned char));
    channels = 1;
  }

  /* use XCALLOC to initialize the dest row_buf so that unspecified
	 pixels in RLE bitmaps show up as the zeroth element in the palette.
  */
  XCALLOC(dest, width * height * channels);
  XMALLOC (row_buf, rowbytes);
  rowstride = width * channels;

  ypos = height - 1;            /* Bitmaps begin in the lower left corner */

  switch (bpp) {

  case 32:
    {
      while (ReadOK (fd, row_buf, rowbytes))
      {
        temp = image + (ypos * rowstride);
        for (xpos = 0; xpos < width; ++xpos) {
			  px32 = ToL(&row_buf[xpos * 4]);
			  unsigned char red = *(temp++) = ((px32 & masks[0].mask) >> masks[0].shiftin) * 255.0 / masks[0].max_value + 0.5;
			  unsigned char green = *(temp++) = ((px32 & masks[1].mask) >> masks[1].shiftin) * 255.0 / masks[1].max_value + 0.5;
			  unsigned char blue = *(temp++) = ((px32 & masks[2].mask) >> masks[2].shiftin) * 255.0 / masks[2].max_value + 0.5;
			  /* currently alpha channels are not supported by AutoTrace, thus simply ignored */
			  /*if (channels > 3)
				  *(temp++) = ((px32 & masks[3].mask) >> masks[3].shiftin) * 255.0 / masks[3].max_value + 0.5;*/
		  }

		  if (ypos == 0)
			  break;

		  --ypos; /* next line */
      }
    }
    break;

  case 24:
    {
      while (ReadOK (fd, row_buf, rowbytes))
      {
        temp = image + (ypos * rowstride);
        for (xpos = 0; xpos < width; ++xpos) {
          *(temp++) = row_buf[xpos * 3 + 2];
          *(temp++) = row_buf[xpos * 3 + 1];
          *(temp++) = row_buf[xpos * 3];
        }
        --ypos;                 /* next line */
      }
    }
    break;

  case 16:
    {
      while (ReadOK (fd, row_buf, rowbytes))
      {
        temp = image + (ypos * rowstride);
        for (xpos = 0; xpos < width; ++xpos)
        {
			  rgb = ToS(&row_buf[xpos * 2]);
			  *(temp++) = ((rgb & masks[0].mask) >> masks[0].shiftin) * 255.0 / masks[0].max_value + 0.5;
			  *(temp++) = ((rgb & masks[1].mask) >> masks[1].shiftin) * 255.0 / masks[1].max_value + 0.5;
			  *(temp++) = ((rgb & masks[2].mask) >> masks[2].shiftin) * 255.0 / masks[2].max_value + 0.5;
			  /* currently alpha channels are not supported by AutoTrace, thus simply ignored */
			  /*if (channels > 3)
				  *(temp++) = ((rgb & masks[3].mask) >> masks[3].shiftin) * 255.0 / masks[3].max_value + 0.5;*/
		  }

		  if (ypos == 0)
			  break;

		  --ypos; /* next line */
      }
    }
    break;

  case 8:
  case 4:
  case 1:
    {
      if (compression == 0) {
        while (ReadOK(fd, &v, 1)) {
          for (i = 1; (i <= (8 / bpp)) && (xpos < width); i++, xpos++) {
			temp = image + (ypos * rowstride) + (xpos * channels);
			*temp = (v & (((1 << bpp) - 1) << (8 - (i*bpp)))) >> (8 - (i*bpp));
			if (Grey)
				*temp = cmap[*temp][0];
	  }

          if (xpos == width) {
	    ReadOK (fd, row_buf, rowbytes - 1 - (width * bpp - 1) / 8);
            ypos--;
            xpos = 0;

          }
          if (ypos < 0)
            break;
        }
        break;
      } else {
	/* compressed image (either RLE8 or RLE4) */
        while (ypos >= 0 && xpos <= width) {
			if (!ReadOK(fd, row_buf, 2))
			{
				LOG("The bitmap ends unexpectedly.");
				break;
			}

			if ((unsigned char) row_buf[0] != 0)
		      /* Count + Color - record */
		      {
				/* encoded mode run -
				 *   row_buf[0] == run_length
				 *   row_buf[1] == pixel data
				 */
		        for (j = 0; ((unsigned char) j < (unsigned char) row_buf[0]) && (xpos < width);)
		          {
#ifdef DEBUG2
              printf("%u %u | ", xpos, width);
#endif
			        for (i = 1;
			             ((i <= (8 / bpp)) &&
			             (xpos < width) &&
			             ((unsigned char) j < (unsigned char) row_buf[0]));
			             i++, xpos++, j++)
			          {
			            temp = dest + (ypos * rowstride) + (xpos * channels);
			            *temp = (unsigned char) ((row_buf[1] & (((1<<bpp)-1) << (8 - (i * bpp)))) >> (8 - (i * bpp)));
				        if (Grey)
					      *temp = cmap[*temp][0];
			          }
            }
          }
          if ((row_buf[0] == 0) && (row_buf[1] > 2))
            /* uncompressed record */
		{
			n = row_buf[1];
			total_bytes_read = 0;

			for (j = 0; j < n; j += (8 / bpp))
			{
				/* read the next byte in the record */
				if (!ReadOK(fd, &v, 1))
				{
					LOG("The bitmap ends unexpectedly.");
					break;
				}

				total_bytes_read++;

				/* read all pixels from that byte */
				i_max = 8 / bpp;
				if (n - j < i_max)
				{
					i_max = n - j;
				}

				i = 1;
				while ((i <= i_max) && (xpos < width))
				{
					temp =
						dest + (ypos * rowstride) + (xpos * channels);
					*temp = (v >> (8 - (i*bpp))) & ((1 << bpp) - 1);
					if (Grey)
						*temp = cmap[*temp][0];
					i++;
					xpos++;
				}
			}

			/* absolute mode runs are padded to 16-bit alignment */
			if (total_bytes_read % 2)
				fread(&v, 1, 1, fd); //ReadOk
		}
	  if (((unsigned char) row_buf[0] == 0) && ((unsigned char) row_buf[1]==0))
            /* Line end */
          {
            ypos--;
            xpos = 0;
          }
          if (((unsigned char)row_buf[0] == 0) && ((unsigned char)row_buf[1] == 1))
            /* Bitmap end */
          {
            break;
          }
          if (((unsigned char)row_buf[0] == 0) && ((unsigned char)row_buf[1] == 2))
            /* Deltarecord */
          {
			if (!ReadOK(fd, row_buf, 2))
			  {
				LOG("The bitmap ends unexpectedly.");
				break;
			  }
			xpos += (unsigned char) row_buf[0];
		    ypos -= (unsigned char) row_buf[1];
          }
        }
        break;
      }
    }
    break;
  default:
    /* This is very bad, we should not be here */
    break;
  }

  if (bpp <= 8) {
    unsigned char *temp2, *temp3;
    unsigned char index;
    temp2 = temp = image;
    XMALLOC (image, width * height * 3 * sizeof (unsigned char)); //???
    temp3 = image;
    for (ypos = 0; ypos < height; ypos++) {
      for (xpos = 0; xpos < width; xpos++) {
        index = *temp2++;
        *temp3++ = cmap[index][0];
        if (!Grey) {
          *temp3++ = cmap[index][1];
          *temp3++ = cmap[index][2];
        }
      }
    }
    free(temp);
  }

  free (row_buf);
  free(dest);
  return image;
}


ReadChannelMasks(unsigned int       *tmp,
	Bitmap_Channel *masks,
	unsigned int          channels)
{
	unsigned int i;

	for (i = 0; i < channels; i++)
	{
		unsigned int mask;
		int    nbits, offset, bit;

		mask = tmp[i];
		masks[i].mask = mask;
		nbits = 0;
		offset = -1;

		for (bit = 0; bit < 32; bit++)
		{
			if (mask & 1)
			{
				nbits++;
				if (offset == -1)
					offset = bit;
			}

			mask = mask >> 1;
		}

		masks[i].shiftin = offset;
		masks[i].max_value = (float)((1 << nbits) - 1);

#ifdef _DEBUG
		LOG4("Channel %d mask %08x in %d max_val %d\n",
			i, masks[i].mask, masks[i].shiftin, (int)masks[i].max_value);
#endif
	}

	return TRUE;
}


static gboolean ReadColorMap(FILE * fd, unsigned char buffer[256][3], int number, int size,
	gboolean *Grey,
	at_exception_type *exp)
{
  int i;
  unsigned char rgb[4];

  *Grey = (number > 2);
  for (i = 0; i < number; i++) {
    if (!ReadOK(fd, rgb, size)) {
      LOG ("Bad colormap\n");
      at_exception_fatal (exp, "Bad colormap");
      return FALSE;
    }

    /* Bitmap save the colors in another order! But change only once! */

    buffer[i][0] = rgb[2];
    buffer[i][1] = rgb[1];
    buffer[i][2] = rgb[0];
    *Grey = ((*Grey) && (rgb[0] == rgb[1]) && (rgb[1] == rgb[2]));
  }
cleanup:
  return TRUE;
}


static void store_line_sample(RzBinSourceLineInfoBuilder *bob, const RzBinDwarfLineHeader *hdr, RzBinDwarfSMRegisters *regs,
	RZ_NULLABLE RzBinDwarfDebugInfo *info, RZ_NULLABLE RzBinDwarfLineFileCache fnc) {
	const char *file = NULL;
	if (regs->file) {
		file = get_full_file_path(info, hdr, fnc, regs->file - 1);
	}
	rz_bin_source_line_info_builder_push_sample(bob, regs->address, (ut32)regs->line, (ut32)regs->column, file);
}

Fix oob write for dwarf with abbrev with count 0 (Fix #2083) (#2086)


static int init_debug_abbrev(RzBinDwarfDebugAbbrev *da) {
	if (!da) {
		return -EINVAL;
	}
	da->decls = calloc(sizeof(RzBinDwarfAbbrevDecl), DEBUG_ABBREV_CAP);
	if (!da->decls) {
		return -ENOMEM;
	}
	da->capacity = DEBUG_ABBREV_CAP;
	da->count = 0;

	return 0;
}


RZ_API const char *rz_bin_dwarf_get_lang_name(ut64 lang) {
	if (lang >= RZ_ARRAY_SIZE(dwarf_langs)) {
		return NULL;
	}
	return dwarf_langs[lang];
}


static inline ut64 dwarf_read_offset(bool is_64bit, bool big_endian, const ut8 **buf, const ut8 *buf_end) {
	ut64 result;
	if (is_64bit) {
		result = READ64(*buf);
	} else {
		result = READ32(*buf);
	}
	return result;
}


static const ut8 *parse_ext_opcode(RzBinDwarfLineOp *op, const RzBinDwarfLineHeader *hdr, const ut8 *obuf, size_t len,
	bool big_endian, ut8 target_addr_size) {
	rz_return_val_if_fail(op && hdr && obuf, NULL);
	const ut8 *buf = obuf;
	const ut8 *buf_end = obuf + len;

	ut64 op_len;
	buf = rz_uleb128(buf, len, &op_len, NULL);
	// op_len must fit and be at least 1 (for the opcode byte)
	if (!buf || buf >= buf_end || !op_len || buf_end - buf < op_len) {
		return NULL;
	}

	ut8 opcode = *buf++;
	op->type = RZ_BIN_DWARF_LINE_OP_TYPE_EXT;
	op->opcode = opcode;

	switch (opcode) {
	case DW_LNE_set_address: {
		ut8 addr_size = hdr->address_size;
		if (hdr->version < 5) { // address_size in header only starting with Dwarf 5
			addr_size = target_addr_size;
		}
		op->args.set_address = dwarf_read_address(addr_size, big_endian, &buf, buf_end);
		break;
	}
	case DW_LNE_define_file: {
		size_t fn_len = rz_str_nlen((const char *)buf, buf_end - buf);
		char *fn = malloc(fn_len + 1);
		if (!fn) {
			return NULL;
		}
		memcpy(fn, buf, fn_len);
		fn[fn_len] = 0;
		op->args.define_file.filename = fn;
		buf += fn_len + 1;
		if (buf + 1 < buf_end) {
			buf = rz_uleb128(buf, buf_end - buf, &op->args.define_file.dir_index, NULL);
		}
		if (buf && buf + 1 < buf_end) {
			buf = rz_uleb128(buf, buf_end - buf, NULL, NULL);
		}
		if (buf && buf + 1 < buf_end) {
			buf = rz_uleb128(buf, buf_end - buf, NULL, NULL);
		}
		break;
	}
	case DW_LNE_set_discriminator:
		buf = rz_uleb128(buf, buf_end - buf, &op->args.set_discriminator, NULL);
		break;
	case DW_LNE_end_sequence:
	default:
		buf += op_len - 1;
		break;
	}
	return buf;
}


RZ_API const char *rz_bin_dwarf_get_attr_form_name(ut64 form_code) {
	if (form_code < DW_FORM_addr || form_code > DW_FORM_addrx4) {
		return NULL;
	}
	return dwarf_attr_form_encodings[form_code];
}


RZ_API RzBinDwarfDebugInfo *rz_bin_dwarf_parse_info(RzBinFile *binfile, RzBinDwarfDebugAbbrev *da) {
	rz_return_val_if_fail(binfile && da, NULL);
	RzBinSection *section = getsection(binfile, "debug_info");
	if (!section) {
		return NULL;
	}

	RzBinDwarfDebugInfo *info = NULL;
	ut64 debug_str_len = 0;
	ut8 *debug_str_buf = NULL;

	RzBinSection *debug_str = debug_str = getsection(binfile, "debug_str");
	if (debug_str) {
		debug_str_len = debug_str->size;
		debug_str_buf = RZ_NEWS0(ut8, debug_str_len + 1);
		if (!debug_str_buf) {
			goto cave;
		}
		st64 ret = rz_buf_read_at(binfile->buf, debug_str->paddr,
			debug_str_buf, debug_str_len);
		if (!ret) {
			goto cave_debug_str_buf;
		}
	}

	ut64 len = section->size;
	if (!len) {
		goto cave_debug_str_buf;
	}
	ut8 *buf = RZ_NEWS0(ut8, len);
	if (!buf) {
		goto cave_debug_str_buf;
	}
	if (!rz_buf_read_at(binfile->buf, section->paddr, buf, len)) {
		goto cave_buf;
	}
	info = parse_info_raw(da, buf, len, debug_str_buf, debug_str_len,
		binfile->o && binfile->o->info && binfile->o->info->big_endian);
	if (!info) {
		goto cave_buf;
	}

	// build hashtable after whole parsing because of possible relocations
	if (info) {
		size_t i, j;
		for (i = 0; i < info->count; i++) {
			RzBinDwarfCompUnit *unit = &info->comp_units[i];
			for (j = 0; j < unit->count; j++) {
				RzBinDwarfDie *die = &unit->dies[j];
				ht_up_insert(info->lookup_table, die->offset, die); // optimization for further processing}
			}
		}
	}
cave_buf:
	free(buf);
cave_debug_str_buf:
	free(debug_str_buf);
cave:
	return info;
}


RZ_API RzList /*<RzBinDwarfARangeSet>*/ *rz_bin_dwarf_parse_aranges(RzBinFile *binfile) {
	rz_return_val_if_fail(binfile, NULL);
	RzBinSection *section = getsection(binfile, "debug_aranges");
	if (!section) {
		return NULL;
	}
	size_t len = section->size;
	if (!len) {
		return NULL;
	}
	ut8 *buf = RZ_NEWS0(ut8, len);
	int ret = rz_buf_read_at(binfile->buf, section->paddr, buf, len);
	if (!ret) {
		free(buf);
		return NULL;
	}
	RzList *r = parse_aranges_raw(buf, len, binfile->o && binfile->o->info && binfile->o->info->big_endian);
	free(buf);
	return r;
}


static inline RzBinDwarfLocList *create_loc_list(ut64 offset) {
	RzBinDwarfLocList *list = RZ_NEW0(RzBinDwarfLocList);
	if (list) {
		list->list = rz_list_new();
		list->offset = offset;
	}
	return list;
}


RZ_API void rz_bin_dwarf_loc_free(HtUP /*<offset, RzBinDwarfLocList*>*/ *loc_table) {
	rz_return_if_fail(loc_table);
	loc_table->opt.freefn = free_loc_table_entry;
	ht_up_free(loc_table);
}


RZ_API bool rz_bin_dwarf_line_op_run(const RzBinDwarfLineHeader *hdr, RzBinDwarfSMRegisters *regs, RzBinDwarfLineOp *op,
	RZ_NULLABLE RzBinSourceLineInfoBuilder *bob, RZ_NULLABLE RzBinDwarfDebugInfo *info, RZ_NULLABLE RzBinDwarfLineFileCache fnc) {
	rz_return_val_if_fail(hdr && regs && op, false);
	switch (op->type) {
	case RZ_BIN_DWARF_LINE_OP_TYPE_STD:
		switch (op->opcode) {
		case DW_LNS_copy:
			if (bob) {
				store_line_sample(bob, hdr, regs, info, fnc);
			}
			regs->basic_block = DWARF_FALSE;
			break;
		case DW_LNS_advance_pc:
			regs->address += op->args.advance_pc * hdr->min_inst_len;
			break;
		case DW_LNS_advance_line:
			regs->line += op->args.advance_line;
			break;
		case DW_LNS_set_file:
			regs->file = op->args.set_file;
			break;
		case DW_LNS_set_column:
			regs->column = op->args.set_column;
			break;
		case DW_LNS_negate_stmt:
			regs->is_stmt = regs->is_stmt ? DWARF_FALSE : DWARF_TRUE;
			break;
		case DW_LNS_set_basic_block:
			regs->basic_block = DWARF_TRUE;
			break;
		case DW_LNS_const_add_pc:
			regs->address += rz_bin_dwarf_line_header_get_spec_op_advance_pc(hdr, 255);
			break;
		case DW_LNS_fixed_advance_pc:
			regs->address += op->args.fixed_advance_pc;
			break;
		case DW_LNS_set_prologue_end:
			regs->prologue_end = ~0;
			break;
		case DW_LNS_set_epilogue_begin:
			regs->epilogue_begin = ~0;
			break;
		case DW_LNS_set_isa:
			regs->isa = op->args.set_isa;
			break;
		default:
			return false;
		}
		break;
	case RZ_BIN_DWARF_LINE_OP_TYPE_EXT:
		switch (op->opcode) {
		case DW_LNE_end_sequence:
			regs->end_sequence = DWARF_TRUE;
			if (bob) {
				// closing entry
				rz_bin_source_line_info_builder_push_sample(bob, regs->address, 0, 0, NULL);
			}
			rz_bin_dwarf_line_header_reset_regs(hdr, regs);
			break;
		case DW_LNE_set_address:
			regs->address = op->args.set_address;
			break;
		case DW_LNE_define_file:
			break;
		case DW_LNE_set_discriminator:
			regs->discriminator = op->args.set_discriminator;
			break;
		default:
			return false;
		}
		break;
	case RZ_BIN_DWARF_LINE_OP_TYPE_SPEC:
		regs->address += rz_bin_dwarf_line_header_get_spec_op_advance_pc(hdr, op->opcode);
		regs->line += rz_bin_dwarf_line_header_get_spec_op_advance_line(hdr, op->opcode);
		if (bob) {
			store_line_sample(bob, hdr, regs, info, fnc);
		}
		regs->basic_block = DWARF_FALSE;
		regs->prologue_end = DWARF_FALSE;
		regs->epilogue_begin = DWARF_FALSE;
		regs->discriminator = 0;
		break;
	default:
		return false;
	}
	return true;
}


static void free_loc_table_entry(HtUPKv *kv) {
	if (kv) {
		free_loc_table_list(kv->value);
	}
}


RZ_API void rz_bin_dwarf_debug_info_free(RzBinDwarfDebugInfo *inf) {
	if (!inf) {
		return;
	}
	for (size_t i = 0; i < inf->count; i++) {
		free_comp_unit(&inf->comp_units[i]);
	}
	ht_up_free(inf->line_info_offset_comp_dir);
	ht_up_free(inf->lookup_table);
	free(inf->comp_units);
	free(inf);
}


RZ_API char *rz_bin_dwarf_line_header_get_full_file_path(RZ_NULLABLE const RzBinDwarfDebugInfo *info, const RzBinDwarfLineHeader *header, ut64 file_index) {
	rz_return_val_if_fail(header, NULL);
	if (file_index >= header->file_names_count) {
		return NULL;
	}
	RzBinDwarfLineFileEntry *file = &header->file_names[file_index];
	if (!file->name) {
		return NULL;
	}

	/*
	 * Dwarf standard does not seem to specify the exact separator (slash/backslash) of paths
	 * so apparently it is target-dependent. However we have yet to see a Windows binary that
	 * also contains dwarf and contains backslashes. The ones we have seen from MinGW have regular
	 * slashes.
	 * And since there seems to be no way to reliable check whether the target uses slashes
	 * or backslashes anyway, we will simply use slashes always here.
	 */

	const char *comp_dir = info ? ht_up_find(info->line_info_offset_comp_dir, header->offset, NULL) : NULL;
	const char *include_dir = NULL;
	char *own_str = NULL;
	if (file->id_idx > 0 && file->id_idx - 1 < header->include_dirs_count) {
		include_dir = header->include_dirs[file->id_idx - 1];
		if (include_dir && include_dir[0] != '/' && comp_dir) {
			include_dir = own_str = rz_str_newf("%s/%s/", comp_dir, include_dir);
		}
	} else {
		include_dir = comp_dir;
	}
	if (!include_dir) {
		include_dir = "./";
	}
	char *r = rz_str_newf("%s/%s", include_dir, file->name);
	free(own_str);
	return r;
}


RZ_API void rz_bin_dwarf_line_op_fini(RzBinDwarfLineOp *op) {
	rz_return_if_fail(op);
	if (op->type == RZ_BIN_DWARF_LINE_OP_TYPE_EXT && op->opcode == DW_LNE_define_file) {
		free(op->args.define_file.filename);
	}
}


static void free_ht_comp_dir(HtUPKv *kv) {
	free(kv->value);
}


static int expand_info(RzBinDwarfDebugInfo *info) {
	rz_return_val_if_fail(info && info->capacity == info->count, -1);

	RzBinDwarfCompUnit *tmp = realloc(info->comp_units,
		info->capacity * 2 * sizeof(RzBinDwarfCompUnit));
	if (!tmp) {
		return -1;
	}

	memset((ut8 *)tmp + info->capacity * sizeof(RzBinDwarfCompUnit),
		0, info->capacity * sizeof(RzBinDwarfCompUnit));

	info->comp_units = tmp;
	info->capacity *= 2;

	return 0;
}


RZ_API const char *rz_bin_dwarf_get_unit_type_name(ut64 unit_type) {
	if (!unit_type || unit_type > DW_UT_split_type) {
		return NULL;
	}
	return dwarf_unit_types[unit_type];
}


static inline ut64 dwarf_read_address(size_t size, bool big_endian, const ut8 **buf, const ut8 *buf_end) {
	ut64 result;
	switch (size) {
	case 2:
		result = READ16(*buf);
		break;
	case 4:
		result = READ32(*buf);
		break;
	case 8:
		result = READ64(*buf);
		break;
	default:
		result = 0;
		*buf += size;
		eprintf("Weird dwarf address size: %zu.", size);
	}
	return result;
}


static const ut8 *fill_block_data(const ut8 *buf, const ut8 *buf_end, RzBinDwarfBlock *block) {
	block->data = calloc(sizeof(ut8), block->length);
	if (!block->data) {
		return NULL;
	}
	/* Maybe unroll this as an optimization in future? */
	if (block->data) {
		size_t j = 0;
		for (j = 0; j < block->length; j++) {
			block->data[j] = READ8(buf);
		}
	}
	return buf;
}


static void line_unit_free(RzBinDwarfLineUnit *unit) {
	if (!unit) {
		return;
	}
	line_header_fini(&unit->header);
	if (unit->ops) {
		for (size_t i = 0; i < unit->ops_count; i++) {
			rz_bin_dwarf_line_op_fini(&unit->ops[i]);
		}
		free(unit->ops);
	}
	free(unit);
}


static const ut8 *parse_comp_unit(RzBinDwarfDebugInfo *info, const ut8 *buf_start,
	RzBinDwarfCompUnit *unit, const RzBinDwarfDebugAbbrev *abbrevs,
	size_t first_abbr_idx, const ut8 *debug_str, size_t debug_str_len, bool big_endian) {

	const ut8 *buf = buf_start;
	const ut8 *buf_end = buf_start + unit->hdr.length - unit->hdr.header_size;

	while (buf && buf < buf_end && buf >= buf_start) {
		if (unit->count && unit->capacity == unit->count) {
			expand_cu(unit);
		}
		RzBinDwarfDie *die = &unit->dies[unit->count];
		// add header size to the offset;
		die->offset = buf - buf_start + unit->hdr.header_size + unit->offset;
		die->offset += unit->hdr.is_64bit ? 12 : 4;

		// DIE starts with ULEB128 with the abbreviation code
		ut64 abbr_code;
		buf = rz_uleb128(buf, buf_end - buf, &abbr_code, NULL);

		if (abbr_code > abbrevs->count || !buf) { // something invalid
			return NULL;
		}

		if (buf >= buf_end) {
			unit->count++; // we wanna store this entry too, usually the last one is null_entry
			return buf; // return the buffer to parse next compilation units
		}
		// there can be "null" entries that have abbr_code == 0
		if (!abbr_code) {
			unit->count++;
			continue;
		}
		ut64 abbr_idx = first_abbr_idx + abbr_code;

		if (abbrevs->count < abbr_idx) {
			return NULL;
		}
		RzBinDwarfAbbrevDecl *abbrev = &abbrevs->decls[abbr_idx - 1];

		if (init_die(die, abbr_code, abbrev->count)) {
			return NULL; // error
		}
		die->tag = abbrev->tag;
		die->has_children = abbrev->has_children;

		buf = parse_die(buf, buf_end, info, abbrev, &unit->hdr, die, debug_str, debug_str_len, big_endian);
		if (!buf) {
			return NULL;
		}

		unit->count++;
	}
	return buf;
}


static const ut8 *info_comp_unit_read_hdr(const ut8 *buf, const ut8 *buf_end, RzBinDwarfCompUnitHdr *hdr, bool big_endian) {
	// 32-bit vs 64-bit dwarf formats
	// http://www.dwarfstd.org/doc/Dwarf3.pdf section 7.4
	hdr->length = READ32(buf);
	if (hdr->length == (ut32)DWARF_INIT_LEN_64) { // then its 64bit
		hdr->length = READ64(buf);
		hdr->is_64bit = true;
	}
	const ut8 *tmp = buf; // to calculate header size
	hdr->version = READ16(buf);
	if (hdr->version == 5) {
		hdr->unit_type = READ8(buf);
		hdr->address_size = READ8(buf);
		hdr->abbrev_offset = dwarf_read_offset(hdr->is_64bit, big_endian, &buf, buf_end);

		if (hdr->unit_type == DW_UT_skeleton || hdr->unit_type == DW_UT_split_compile) {
			hdr->dwo_id = READ8(buf);
		} else if (hdr->unit_type == DW_UT_type || hdr->unit_type == DW_UT_split_type) {
			hdr->type_sig = READ64(buf);
			hdr->type_offset = dwarf_read_offset(hdr->is_64bit, big_endian, &buf, buf_end);
		}
	} else {
		hdr->abbrev_offset = dwarf_read_offset(hdr->is_64bit, big_endian, &buf, buf_end);
		hdr->address_size = READ8(buf);
	}
	hdr->header_size = buf - tmp; // header size excluding length field
	return buf;
}


static ut8 *get_section_bytes(RzBinFile *binfile, const char *sect_name, size_t *len) {
	rz_return_val_if_fail(binfile && sect_name && len, NULL);
	RzBinSection *section = getsection(binfile, sect_name);
	if (!section) {
		return NULL;
	}
	if (section->size > binfile->size) {
		return NULL;
	}
	*len = section->size;
	ut8 *buf = calloc(1, *len);
	rz_buf_read_at(binfile->buf, section->paddr, buf, *len);
	return buf;
}


RZ_API const char *rz_bin_dwarf_get_tag_name(ut64 tag) {
	if (tag >= DW_TAG_LAST) {
		return NULL;
	}
	return dwarf_tag_name_encodings[tag];
}


RZ_API void rz_bin_dwarf_line_info_free(RzBinDwarfLineInfo *li) {
	if (!li) {
		return;
	}
	rz_list_free(li->units);
	rz_bin_source_line_info_free(li->lines);
	free(li);
}


static bool init_debug_info(RzBinDwarfDebugInfo *inf) {
	inf->comp_units = RZ_NEWS0(RzBinDwarfCompUnit, DEBUG_INFO_CAPACITY);
	if (!inf->comp_units) {
		return false;
	}
	inf->lookup_table = ht_up_new0();
	if (!inf->lookup_table) {
		goto wurzelbert_comp_units;
	}
	inf->line_info_offset_comp_dir = ht_up_new(NULL, free_ht_comp_dir, NULL);
	if (!inf->line_info_offset_comp_dir) {
		goto wurzelbert_lookup_table;
	}
	inf->capacity = DEBUG_INFO_CAPACITY;
	inf->count = 0;
	return true;
wurzelbert_lookup_table:
	ht_up_free(inf->lookup_table);
wurzelbert_comp_units:
	free(inf->comp_units);
	return false;
}


static RzBinDwarfDebugAbbrev *parse_abbrev_raw(const ut8 *obuf, size_t len) {
	const ut8 *buf = obuf, *buf_end = obuf + len;
	ut64 tmp, attr_code, attr_form, offset;
	st64 special;
	ut8 has_children;
	RzBinDwarfAbbrevDecl *tmpdecl;

	// XXX - Set a suitable value here.
	if (!obuf || len < 3) {
		return NULL;
	}
	RzBinDwarfDebugAbbrev *da = RZ_NEW0(RzBinDwarfDebugAbbrev);

	init_debug_abbrev(da);

	while (buf && buf + 1 < buf_end) {
		offset = buf - obuf;
		buf = rz_uleb128(buf, (size_t)(buf_end - buf), &tmp, NULL);
		if (!buf || !tmp || buf >= buf_end) {
			continue;
		}
		if (da->count == da->capacity) {
			expand_debug_abbrev(da);
		}
		tmpdecl = &da->decls[da->count];
		init_abbrev_decl(tmpdecl);

		tmpdecl->code = tmp;
		buf = rz_uleb128(buf, (size_t)(buf_end - buf), &tmp, NULL);
		tmpdecl->tag = tmp;

		tmpdecl->offset = offset;
		if (buf >= buf_end) {
			break;
		}
		has_children = READ8(buf);
		tmpdecl->has_children = has_children;
		do {
			if (tmpdecl->count == tmpdecl->capacity) {
				expand_abbrev_decl(tmpdecl);
			}
			buf = rz_uleb128(buf, (size_t)(buf_end - buf), &attr_code, NULL);
			if (buf >= buf_end) {
				break;
			}
			buf = rz_uleb128(buf, (size_t)(buf_end - buf), &attr_form, NULL);
			// http://www.dwarfstd.org/doc/DWARF5.pdf#page=225
			if (attr_form == DW_FORM_implicit_const) {
				buf = rz_leb128(buf, (size_t)(buf_end - buf), &special);
				tmpdecl->defs[tmpdecl->count].special = special;
			}
			tmpdecl->defs[tmpdecl->count].attr_name = attr_code;
			tmpdecl->defs[tmpdecl->count].attr_form = attr_form;
			tmpdecl->count++;
		} while (attr_code && attr_form);

		da->count++;
	}
	return da;
}


RZ_API RzBinDwarfLineInfo *rz_bin_dwarf_parse_line(RzBinFile *binfile, RZ_NULLABLE RzBinDwarfDebugInfo *info, RzBinDwarfLineInfoMask mask) {
	rz_return_val_if_fail(binfile, NULL);
	RzBinSection *section = getsection(binfile, "debug_line");
	if (!section) {
		return NULL;
	}
	ut64 len = section->size;
	if (len < 1) {
		return NULL;
	}
	ut8 *buf = RZ_NEWS0(ut8, len + 1);
	if (!buf) {
		return NULL;
	}
	int ret = rz_buf_read_at(binfile->buf, section->paddr, buf, len);
	if (ret != len) {
		free(buf);
		return NULL;
	}
	// Actually parse the section
	RzBinDwarfLineInfo *r = parse_line_raw(binfile, buf, len, mask, binfile->o && binfile->o->info && binfile->o->info->big_endian, info);
	free(buf);
	return r;
}


static RzList /*<RzBinDwarfARangeSet>*/ *parse_aranges_raw(const ut8 *obuf, size_t obuf_sz, bool big_endian) {
	rz_return_val_if_fail(obuf, NULL);
	const ut8 *buf = obuf;
	const ut8 *buf_end = buf + obuf_sz;

	RzList *r = rz_list_newf((RzListFree)rz_bin_dwarf_arange_set_free);
	if (!r) {
		return NULL;
	}

	// DWARF 3 Standard Section 6.1.2 Lookup by Address
	// also useful to grep for display_debug_aranges in binutils
	while (buf < buf_end) {
		const ut8 *start = buf;
		bool is_64bit;
		ut64 unit_length = dwarf_read_initial_length(&is_64bit, big_endian, &buf, buf_end);
		// Sanity check: length must be at least the minimal size of the remaining header fields
		// and at maximum the remaining buffer size.
		size_t header_rest_size = 2 + (is_64bit ? 8 : 4) + 1 + 1;
		if (unit_length < header_rest_size || unit_length > buf_end - buf) {
			break;
		}
		const ut8 *next_set_buf = buf + unit_length;
		RzBinDwarfARangeSet *set = RZ_NEW(RzBinDwarfARangeSet);
		if (!set) {
			break;
		}
		set->unit_length = unit_length;
		set->is_64bit = is_64bit;
		set->version = READ16(buf);
		set->debug_info_offset = dwarf_read_offset(set->is_64bit, big_endian, &buf, buf_end);
		set->address_size = READ8(buf);
		set->segment_size = READ8(buf);
		unit_length -= header_rest_size;
		if (!set->address_size) {
			free(set);
			break;
		}

		// align to 2*addr_size
		size_t off = buf - start;
		size_t pad = rz_num_align_delta(off, 2 * set->address_size);
		if (pad > unit_length || pad > buf_end - buf) {
			free(set);
			break;
		}
		buf += pad;
		unit_length -= pad;

		size_t arange_size = 2 * set->address_size;
		set->aranges_count = unit_length / arange_size;
		if (!set->aranges_count) {
			free(set);
			break;
		}
		set->aranges = RZ_NEWS0(RzBinDwarfARange, set->aranges_count);
		if (!set->aranges) {
			free(set);
			break;
		}
		size_t i;
		for (i = 0; i < set->aranges_count; i++) {
			set->aranges[i].addr = dwarf_read_address(set->address_size, big_endian, &buf, buf_end);
			set->aranges[i].length = dwarf_read_address(set->address_size, big_endian, &buf, buf_end);
			if (!set->aranges[i].addr && !set->aranges[i].length) {
				// last entry has two 0s
				i++; // so i will be the total count of read entries
				break;
			}
		}
		set->aranges_count = i;
		buf = next_set_buf;
		rz_list_push(r, set);
	}

	return r;
}


static const ut8 *parse_line_header(
	RzBinFile *bf, const ut8 *buf, const ut8 *buf_end,
	RzBinDwarfLineHeader *hdr, ut64 offset_cur, bool big_endian) {
	rz_return_val_if_fail(hdr && bf && buf && buf_end, NULL);

	hdr->offset = offset_cur;
	hdr->is_64bit = false;
	hdr->unit_length = dwarf_read_initial_length(&hdr->is_64bit, big_endian, &buf, buf_end);
	hdr->version = READ16(buf);

	if (hdr->version == 5) {
		hdr->address_size = READ8(buf);
		hdr->segment_selector_size = READ8(buf);
	}

	hdr->header_length = dwarf_read_offset(hdr->is_64bit, big_endian, &buf, buf_end);

	const ut8 *tmp_buf = buf; // So I can skip parsing DWARF 5 headers for now

	if (buf_end - buf < 8) {
		return NULL;
	}
	hdr->min_inst_len = READ8(buf);
	if (hdr->version >= 4) {
		hdr->max_ops_per_inst = READ8(buf);
	}
	hdr->default_is_stmt = READ8(buf);
	hdr->line_base = (st8)READ8(buf);
	hdr->line_range = READ8(buf);
	hdr->opcode_base = READ8(buf);

	hdr->file_names = NULL;

	if (hdr->opcode_base > 1) {
		hdr->std_opcode_lengths = calloc(sizeof(ut8), hdr->opcode_base - 1);
		for (size_t i = 1; i < hdr->opcode_base; i++) {
			if (buf + 2 > buf_end) {
				hdr->opcode_base = i;
				break;
			}
			hdr->std_opcode_lengths[i - 1] = READ8(buf);
		}
	} else {
		hdr->std_opcode_lengths = NULL;
	}
	// TODO finish parsing of source files out of DWARF 5 header
	// for now we skip
	if (hdr->version == 5) {
		tmp_buf += hdr->header_length;
		return tmp_buf;
	}

	if (hdr->version <= 4) {
		buf = parse_line_header_source(bf, buf, buf_end, hdr);
	} else {
		buf = NULL;
	}

	return buf;
}


RZ_API void rz_bin_dwarf_line_header_reset_regs(const RzBinDwarfLineHeader *hdr, RzBinDwarfSMRegisters *regs) {
	rz_return_if_fail(hdr && regs);
	regs->address = 0;
	regs->file = 1;
	regs->line = 1;
	regs->column = 0;
	regs->is_stmt = hdr->default_is_stmt;
	regs->basic_block = DWARF_FALSE;
	regs->end_sequence = DWARF_FALSE;
	regs->prologue_end = DWARF_FALSE;
	regs->epilogue_begin = DWARF_FALSE;
	regs->isa = 0;
}


static size_t parse_opcodes(const ut8 *obuf,
	size_t len, const RzBinDwarfLineHeader *hdr, RzVector *ops_out,
	RzBinDwarfSMRegisters *regs, RZ_NULLABLE RzBinSourceLineInfoBuilder *bob, RZ_NULLABLE RzBinDwarfDebugInfo *info,
	RZ_NULLABLE RzBinDwarfLineFileCache fnc, bool big_endian, ut8 target_addr_size) {
	const ut8 *buf, *buf_end;
	ut8 opcode;

	if (!obuf || !len) {
		return 0;
	}
	buf = obuf;
	buf_end = obuf + len;

	while (buf < buf_end) {
		opcode = *buf++;
		RzBinDwarfLineOp op = { 0 };
		if (!opcode) {
			buf = parse_ext_opcode(&op, hdr, buf, (buf_end - buf), big_endian, target_addr_size);
		} else if (opcode >= hdr->opcode_base) {
			// special opcode without args, no further parsing needed
			op.type = RZ_BIN_DWARF_LINE_OP_TYPE_SPEC;
			op.opcode = opcode;
		} else {
			buf = parse_std_opcode(&op, hdr, buf, (buf_end - buf), opcode, big_endian);
		}
		if (!buf) {
			break;
		}
		if (bob) {
			rz_bin_dwarf_line_op_run(hdr, regs, &op, bob, info, fnc);
		}
		if (ops_out) {
			rz_vector_push(ops_out, &op);
		} else {
			rz_bin_dwarf_line_op_fini(&op);
		}
	}
	if (!buf) {
		return 0;
	}
	return (size_t)(buf - obuf); // number of bytes we've moved by
}