gif_internal_decode_frame(gif_animation *gif,
                          unsigned int frame,
                          bool clear_image)
{
        unsigned int index = 0;
        const unsigned char *gif_data, *gif_end;
        ssize_t gif_bytes;
        unsigned int width, height, offset_x, offset_y;
        unsigned int flags, colour_table_size, interlace;
        unsigned int *colour_table;
        unsigned int *frame_data = 0;	// Set to 0 for no warnings
        unsigned int *frame_scanline;
        ssize_t save_buffer_position;
        unsigned int return_value = 0;
        unsigned int x, y, decode_y, burst_bytes;
        register unsigned char colour;

        /* If the GIF has no frame data, frame holders will not be allocated in
         * gif_initialise() */
        if (gif->frames == NULL) {
                return GIF_INSUFFICIENT_DATA;
        }

        /* Ensure this frame is supposed to be decoded */
        if (gif->frames[frame].display == false) {
                return GIF_OK;
        }

        /* Ensure the frame is in range to decode */
        if (frame > gif->frame_count_partial) {
                return GIF_INSUFFICIENT_DATA;
        }

        /* done if frame is already decoded */
        if ((!clear_image) &&
            ((int)frame == gif->decoded_frame)) {
                return GIF_OK;
        }

        /* Get the start of our frame data and the end of the GIF data */
        gif_data = gif->gif_data + gif->frames[frame].frame_pointer;
        gif_end = gif->gif_data + gif->buffer_size;
        gif_bytes = (gif_end - gif_data);

        /*
         * Ensure there is a minimal amount of data to proceed.  The shortest
         * block of data is a 10-byte image descriptor + 1-byte gif trailer
         */
        if (gif_bytes < 12) {
                return GIF_INSUFFICIENT_FRAME_DATA;
        }

        /* Save the buffer position */
        save_buffer_position = gif->buffer_position;
        gif->buffer_position = gif_data - gif->gif_data;

        /* Skip any extensions because they have allready been processed */
        if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) {
                goto gif_decode_frame_exit;
        }
        gif_data = (gif->gif_data + gif->buffer_position);
        gif_bytes = (gif_end - gif_data);

        /* Ensure we have enough data for the 10-byte image descriptor + 1-byte
         * gif trailer
         */
        if (gif_bytes < 12) {
                return_value = GIF_INSUFFICIENT_FRAME_DATA;
                goto gif_decode_frame_exit;
        }

        /* 10-byte Image Descriptor is:
         *
         *	+0	CHAR	Image Separator (0x2c)
         *	+1	SHORT	Image Left Position
         *	+3	SHORT	Image Top Position
         *	+5	SHORT	Width
         *	+7	SHORT	Height
         *	+9	CHAR	__Packed Fields__
         *			1BIT	Local Colour Table Flag
         *			1BIT	Interlace Flag
         *			1BIT	Sort Flag
         *			2BITS	Reserved
         *			3BITS	Size of Local Colour Table
         */
        if (gif_data[0] != GIF_IMAGE_SEPARATOR) {
                return_value = GIF_DATA_ERROR;
                goto gif_decode_frame_exit;
        }
        offset_x = gif_data[1] | (gif_data[2] << 8);
        offset_y = gif_data[3] | (gif_data[4] << 8);
        width = gif_data[5] | (gif_data[6] << 8);
        height = gif_data[7] | (gif_data[8] << 8);

        /* Boundary checking - shouldn't ever happen except unless the data has
         * been modified since initialisation.
         */
        if ((offset_x + width > gif->width) ||
            (offset_y + height > gif->height)) {
                return_value = GIF_DATA_ERROR;
                goto gif_decode_frame_exit;
        }

        /* Decode the flags */
        flags = gif_data[9];
        colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK);
        interlace = flags & GIF_INTERLACE_MASK;

        /* Advance data pointer to next block either colour table or image
         * data.
         */
        gif_data += 10;
        gif_bytes = (gif_end - gif_data);

        /* Set up the colour table */
        if (flags & GIF_COLOUR_TABLE_MASK) {
                if (gif_bytes < (int)(3 * colour_table_size)) {
                        return_value = GIF_INSUFFICIENT_FRAME_DATA;
                        goto gif_decode_frame_exit;
                }
                colour_table = gif->local_colour_table;
                if (!clear_image) {
                        for (index = 0; index < colour_table_size; index++) {
                                /* Gif colour map contents are r,g,b.
                                 *
                                 * We want to pack them bytewise into the
                                 * colour table, such that the red component
                                 * is in byte 0 and the alpha component is in
                                 * byte 3.
                                 */
                                unsigned char *entry =
                                        (unsigned char *) &colour_table[index];

                                entry[0] = gif_data[0];	/* r */
                                entry[1] = gif_data[1];	/* g */
                                entry[2] = gif_data[2];	/* b */
                                entry[3] = 0xff;	/* a */

                                gif_data += 3;
                        }
                } else {
                        gif_data += 3 * colour_table_size;
                }
                gif_bytes = (gif_end - gif_data);
        } else {
                colour_table = gif->global_colour_table;
        }

        /* Ensure sufficient data remains */
        if (gif_bytes < 1) {
                return_value = GIF_INSUFFICIENT_FRAME_DATA;
                goto gif_decode_frame_exit;
        }

        /* check for an end marker */
        if (gif_data[0] == GIF_TRAILER) {
                return_value = GIF_OK;
                goto gif_decode_frame_exit;
        }

        /* Get the frame data */
        assert(gif->bitmap_callbacks.bitmap_get_buffer);
        frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
        if (!frame_data) {
                return GIF_INSUFFICIENT_MEMORY;
        }

        /* If we are clearing the image we just clear, if not decode */
        if (!clear_image) {
                lzw_result res;
                const uint8_t *stack_base;
                const uint8_t *stack_pos;

                /* Ensure we have enough data for a 1-byte LZW code size +
                 * 1-byte gif trailer
                 */
                if (gif_bytes < 2) {
                        return_value = GIF_INSUFFICIENT_FRAME_DATA;
                        goto gif_decode_frame_exit;
                }

                /* If we only have a 1-byte LZW code size + 1-byte gif trailer,
                 * we're finished
                 */
                if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) {
                        return_value = GIF_OK;
                        goto gif_decode_frame_exit;
                }

                /* If the previous frame's disposal method requires we restore
                 * the background colour or this is the first frame, clear
                 * the frame data
                 */
                if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) {
                        memset((char*)frame_data,
                               GIF_TRANSPARENT_COLOUR,
                               gif->width * gif->height * sizeof(int));
                        gif->decoded_frame = frame;
                        /* The line below would fill the image with its
                         * background color, but because GIFs support
                         * transparency we likely wouldn't want to do that. */
                        /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */
                } else if ((frame != 0) &&
                           (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) {
                        return_value = gif_internal_decode_frame(gif,
                                                                 (frame - 1),
                                                                 true);
                        if (return_value != GIF_OK) {
                                goto gif_decode_frame_exit;
                        }

                } else if ((frame != 0) &&
                           (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) {
                        /*
                         * If the previous frame's disposal method requires we
                         * restore the previous image, find the last image set
                         * to "do not dispose" and get that frame data
                         */
                        int last_undisposed_frame = frame - 2;
                        while ((last_undisposed_frame >= 0) &&
                               (gif->frames[last_undisposed_frame].disposal_method == GIF_FRAME_RESTORE)) {
                                last_undisposed_frame--;
                        }

                        /* If we don't find one, clear the frame data */
                        if (last_undisposed_frame == -1) {
                                /* see notes above on transparency
                                 * vs. background color
                                 */
                                memset((char*)frame_data,
                                       GIF_TRANSPARENT_COLOUR,
                                       gif->width * gif->height * sizeof(int));
                        } else {
                                return_value = gif_internal_decode_frame(gif, last_undisposed_frame, false);
                                if (return_value != GIF_OK) {
                                        goto gif_decode_frame_exit;
                                }
                                /* Get this frame's data */
                                assert(gif->bitmap_callbacks.bitmap_get_buffer);
                                frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image);
                                if (!frame_data) {
                                        return GIF_INSUFFICIENT_MEMORY;
                                }
                        }
                }
                gif->decoded_frame = frame;
                gif->buffer_position = (gif_data - gif->gif_data) + 1;

                /* Initialise the LZW decoding */
                res = lzw_decode_init(gif->lzw_ctx, gif->gif_data,
                                gif->buffer_size, gif->buffer_position,
                                gif_data[0], &stack_base, &stack_pos);
                if (res != LZW_OK) {
                        return gif_error_from_lzw(res);
                }

                /* Decompress the data */
                for (y = 0; y < height; y++) {
                        if (interlace) {
                                decode_y = gif_interlaced_line(height, y) + offset_y;
                        } else {
                                decode_y = y + offset_y;
                        }
                        frame_scanline = frame_data + offset_x + (decode_y * gif->width);

                        /* Rather than decoding pixel by pixel, we try to burst
                         * out streams of data to remove the need for end-of
                         * data checks every pixel.
                         */
                        x = width;
                        while (x > 0) {
                                burst_bytes = (stack_pos - stack_base);
                                if (burst_bytes > 0) {
                                        if (burst_bytes > x) {
                                                burst_bytes = x;
                                        }
                                        x -= burst_bytes;
                                        while (burst_bytes-- > 0) {
                                                colour = *--stack_pos;
                                                if (((gif->frames[frame].transparency) &&
                                                     (colour != gif->frames[frame].transparency_index)) ||
                                                    (!gif->frames[frame].transparency)) {
                                                        *frame_scanline = colour_table[colour];
                                                }
                                                frame_scanline++;
                                        }
                                } else {
                                        res = lzw_decode(gif->lzw_ctx, &stack_pos);
                                        if (res != LZW_OK) {
                                                /* Unexpected end of frame, try to recover */
                                                if (res == LZW_OK_EOD) {
                                                        return_value = GIF_OK;
                                                } else {
                                                        return_value = gif_error_from_lzw(res);
                                                }
                                                goto gif_decode_frame_exit;
                                        }
                                }
                        }
                }
        } else {
                /* Clear our frame */
                if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) {
                        for (y = 0; y < height; y++) {
                                frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width);
                                if (gif->frames[frame].transparency) {
                                        memset(frame_scanline,
                                               GIF_TRANSPARENT_COLOUR,
                                               width * 4);
                                } else {
                                        memset(frame_scanline,
                                               colour_table[gif->background_index],
                                               width * 4);
                                }
                        }
                }
        }
gif_decode_frame_exit:

        /* Check if we should test for optimisation */
        if (gif->frames[frame].virgin) {
                if (gif->bitmap_callbacks.bitmap_test_opaque) {
                        gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image);
                } else {
                        gif->frames[frame].opaque = false;
                }
                gif->frames[frame].virgin = false;
        }

        if (gif->bitmap_callbacks.bitmap_set_opaque) {
                gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque);
        }

        if (gif->bitmap_callbacks.bitmap_modified) {
                gif->bitmap_callbacks.bitmap_modified(gif->frame_image);
        }

        /* Restore the buffer position */
        gif->buffer_position = save_buffer_position;

        return return_value;
}


PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
{
    int chr = *scanner->curptr;

    if (!chr) {
	pj_scan_syntax_err(scanner);
	return 0;
    }

    ++scanner->curptr;

    if (PJ_SCAN_IS_PROBABLY_SPACE(*scanner->curptr) && scanner->skip_ws) {
	pj_scan_skip_whitespace(scanner);
    }
    return chr;
}

Merge pull request from GHSA-7fw8-54cv-r7pm


PJ_DEF(int) pj_scan_get_char( pj_scanner *scanner )
{
    register char *s = scanner->curptr;
    int chr;

    if (s >= scanner->end || !*s) {
	pj_scan_syntax_err(scanner);
	return 0;
    }

    chr = *s;

    ++s;
    scanner->curptr = s;
    if (PJ_SCAN_CHECK_EOF(s) && PJ_SCAN_IS_PROBABLY_SPACE(*s) &&
    	scanner->skip_ws)
    {
	pj_scan_skip_whitespace(scanner);
    }
    return chr;
}


static bool load_buffer(RBinFile *bf, void **bin_obj, RBuffer *buf, ut64 loadaddr, Sdb *sdb) {
	RBuffer *fbuf = r_buf_ref (buf);
	struct MACH0_(opts_t) opts;
	MACH0_(opts_set_default) (&opts, bf);
	struct MACH0_(obj_t) *main_mach0 = MACH0_(new_buf) (fbuf, &opts);
	if (!main_mach0) {
		return false;
	}

	RRebaseInfo *rebase_info = r_rebase_info_new_from_mach0 (fbuf, main_mach0);
	RKernelCacheObj *obj = NULL;

	RPrelinkRange *prelink_range = get_prelink_info_range_from_mach0 (main_mach0);
	if (!prelink_range) {
		goto beach;
	}

	obj = R_NEW0 (RKernelCacheObj);
	if (!obj) {
		R_FREE (prelink_range);
		goto beach;
	}

	RCFValueDict *prelink_info = NULL;
	if (main_mach0->hdr.filetype != MH_FILESET && prelink_range->range.size) {
		prelink_info = r_cf_value_dict_parse (fbuf, prelink_range->range.offset,
				prelink_range->range.size, R_CF_OPTION_SKIP_NSDATA);
		if (!prelink_info) {
			R_FREE (prelink_range);
			R_FREE (obj);
			goto beach;
		}
	}

	if (!pending_bin_files) {
		pending_bin_files = r_list_new ();
		if (!pending_bin_files) {
			R_FREE (prelink_range);
			R_FREE (obj);
			R_FREE (prelink_info);
			goto beach;
		}
	}

	obj->mach0 = main_mach0;
	obj->rebase_info = rebase_info;
	obj->prelink_info = prelink_info;
	obj->cache_buf = fbuf;
	obj->pa2va_exec = prelink_range->pa2va_exec;
	obj->pa2va_data = prelink_range->pa2va_data;

	R_FREE (prelink_range);

	*bin_obj = obj;

	r_list_push (pending_bin_files, bf);

	if (rebase_info || main_mach0->chained_starts) {
		RIO *io = bf->rbin->iob.io;
		swizzle_io_read (obj, io);
	}

	return true;

beach:
	r_buf_free (fbuf);
	obj->cache_buf = NULL;
	MACH0_(mach0_free) (main_mach0);
	return false;
}

Fix null deref in xnu.kernelcache ##crash

* Reported by @xshad3 via huntr.dev


static bool load_buffer(RBinFile *bf, void **bin_obj, RBuffer *buf, ut64 loadaddr, Sdb *sdb) {
	RBuffer *fbuf = r_buf_ref (buf);
	struct MACH0_(opts_t) opts;
	MACH0_(opts_set_default) (&opts, bf);
	struct MACH0_(obj_t) *main_mach0 = MACH0_(new_buf) (fbuf, &opts);
	if (!main_mach0) {
		return false;
	}

	RRebaseInfo *rebase_info = r_rebase_info_new_from_mach0 (fbuf, main_mach0);
	RKernelCacheObj *obj = NULL;

	RPrelinkRange *prelink_range = get_prelink_info_range_from_mach0 (main_mach0);
	if (!prelink_range) {
		goto beach;
	}

	obj = R_NEW0 (RKernelCacheObj);
	if (!obj) {
		R_FREE (prelink_range);
		goto beach;
	}

	RCFValueDict *prelink_info = NULL;
	if (main_mach0->hdr.filetype != MH_FILESET && prelink_range->range.size) {
		prelink_info = r_cf_value_dict_parse (fbuf, prelink_range->range.offset,
				prelink_range->range.size, R_CF_OPTION_SKIP_NSDATA);
		if (!prelink_info) {
			R_FREE (prelink_range);
			R_FREE (obj);
			goto beach;
		}
	}

	if (!pending_bin_files) {
		pending_bin_files = r_list_new ();
		if (!pending_bin_files) {
			R_FREE (prelink_range);
			R_FREE (obj);
			R_FREE (prelink_info);
			goto beach;
		}
	}

	obj->mach0 = main_mach0;
	obj->rebase_info = rebase_info;
	obj->prelink_info = prelink_info;
	obj->cache_buf = fbuf;
	obj->pa2va_exec = prelink_range->pa2va_exec;
	obj->pa2va_data = prelink_range->pa2va_data;

	R_FREE (prelink_range);

	*bin_obj = obj;

	r_list_push (pending_bin_files, bf);

	if (rebase_info || main_mach0->chained_starts) {
		RIO *io = bf->rbin->iob.io;
		swizzle_io_read (obj, io);
	}

	return true;

beach:
	r_buf_free (fbuf);
	if (obj) {
		obj->cache_buf = NULL;
	}
	MACH0_(mach0_free) (main_mach0);
	return false;
}


ex_retab(exarg_T *eap)
{
    linenr_T	lnum;
    int		got_tab = FALSE;
    long	num_spaces = 0;
    long	num_tabs;
    long	len;
    long	col;
    long	vcol;
    long	start_col = 0;		// For start of white-space string
    long	start_vcol = 0;		// For start of white-space string
    long	old_len;
    char_u	*ptr;
    char_u	*new_line = (char_u *)1; // init to non-NULL
    int		did_undo;		// called u_save for current line
#ifdef FEAT_VARTABS
    int		*new_vts_array = NULL;
    char_u	*new_ts_str;		// string value of tab argument
#else
    int		temp;
    int		new_ts;
#endif
    int		save_list;
    linenr_T	first_line = 0;		// first changed line
    linenr_T	last_line = 0;		// last changed line

    save_list = curwin->w_p_list;
    curwin->w_p_list = 0;	    // don't want list mode here

#ifdef FEAT_VARTABS
    new_ts_str = eap->arg;
    if (tabstop_set(eap->arg, &new_vts_array) == FAIL)
	return;
    while (vim_isdigit(*(eap->arg)) || *(eap->arg) == ',')
	++(eap->arg);

    // This ensures that either new_vts_array and new_ts_str are freshly
    // allocated, or new_vts_array points to an existing array and new_ts_str
    // is null.
    if (new_vts_array == NULL)
    {
	new_vts_array = curbuf->b_p_vts_array;
	new_ts_str = NULL;
    }
    else
	new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
#else
    ptr = eap->arg;
    new_ts = getdigits(&ptr);
    if (new_ts < 0 && *eap->arg == '-')
    {
	emsg(_(e_argument_must_be_positive));
	return;
    }
    if (new_ts < 0 || new_ts > TABSTOP_MAX)
    {
	semsg(_(e_invalid_argument_str), eap->arg);
	return;
    }
    if (new_ts == 0)
	new_ts = curbuf->b_p_ts;
#endif
    for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
    {
	ptr = ml_get(lnum);
	col = 0;
	vcol = 0;
	did_undo = FALSE;
	for (;;)
	{
	    if (VIM_ISWHITE(ptr[col]))
	    {
		if (!got_tab && num_spaces == 0)
		{
		    // First consecutive white-space
		    start_vcol = vcol;
		    start_col = col;
		}
		if (ptr[col] == ' ')
		    num_spaces++;
		else
		    got_tab = TRUE;
	    }
	    else
	    {
		if (got_tab || (eap->forceit && num_spaces > 1))
		{
		    // Retabulate this string of white-space

		    // len is virtual length of white string
		    len = num_spaces = vcol - start_vcol;
		    num_tabs = 0;
		    if (!curbuf->b_p_et)
		    {
#ifdef FEAT_VARTABS
			int t, s;

			tabstop_fromto(start_vcol, vcol,
					curbuf->b_p_ts, new_vts_array, &t, &s);
			num_tabs = t;
			num_spaces = s;
#else
			temp = new_ts - (start_vcol % new_ts);
			if (num_spaces >= temp)
			{
			    num_spaces -= temp;
			    num_tabs++;
			}
			num_tabs += num_spaces / new_ts;
			num_spaces -= (num_spaces / new_ts) * new_ts;
#endif
		    }
		    if (curbuf->b_p_et || got_tab ||
					(num_spaces + num_tabs < len))
		    {
			if (did_undo == FALSE)
			{
			    did_undo = TRUE;
			    if (u_save((linenr_T)(lnum - 1),
						(linenr_T)(lnum + 1)) == FAIL)
			    {
				new_line = NULL;	// flag out-of-memory
				break;
			    }
			}

			// len is actual number of white characters used
			len = num_spaces + num_tabs;
			old_len = (long)STRLEN(ptr);
			new_line = alloc(old_len - col + start_col + len + 1);
			if (new_line == NULL)
			    break;
			if (start_col > 0)
			    mch_memmove(new_line, ptr, (size_t)start_col);
			mch_memmove(new_line + start_col + len,
				      ptr + col, (size_t)(old_len - col + 1));
			ptr = new_line + start_col;
			for (col = 0; col < len; col++)
			    ptr[col] = (col < num_tabs) ? '\t' : ' ';
			if (ml_replace(lnum, new_line, FALSE) == OK)
			    // "new_line" may have been copied
			    new_line = curbuf->b_ml.ml_line_ptr;
			if (first_line == 0)
			    first_line = lnum;
			last_line = lnum;
			ptr = new_line;
			col = start_col + len;
		    }
		}
		got_tab = FALSE;
		num_spaces = 0;
	    }
	    if (ptr[col] == NUL)
		break;
	    vcol += chartabsize(ptr + col, (colnr_T)vcol);
	    if (has_mbyte)
		col += (*mb_ptr2len)(ptr + col);
	    else
		++col;
	}
	if (new_line == NULL)		    // out of memory
	    break;
	line_breakcheck();
    }
    if (got_int)
	emsg(_(e_interrupted));

#ifdef FEAT_VARTABS
    // If a single value was given then it can be considered equal to
    // either the value of 'tabstop' or the value of 'vartabstop'.
    if (tabstop_count(curbuf->b_p_vts_array) == 0
	&& tabstop_count(new_vts_array) == 1
	&& curbuf->b_p_ts == tabstop_first(new_vts_array))
	; // not changed
    else if (tabstop_count(curbuf->b_p_vts_array) > 0
        && tabstop_eq(curbuf->b_p_vts_array, new_vts_array))
	; // not changed
    else
	redraw_curbuf_later(NOT_VALID);
#else
    if (curbuf->b_p_ts != new_ts)
	redraw_curbuf_later(NOT_VALID);
#endif
    if (first_line != 0)
	changed_lines(first_line, 0, last_line + 1, 0L);

    curwin->w_p_list = save_list;	// restore 'list'

#ifdef FEAT_VARTABS
    if (new_ts_str != NULL)		// set the new tabstop
    {
	// If 'vartabstop' is in use or if the value given to retab has more
	// than one tabstop then update 'vartabstop'.
	int *old_vts_ary = curbuf->b_p_vts_array;

	if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1)
	{
	    set_string_option_direct((char_u *)"vts", -1, new_ts_str,
							OPT_FREE|OPT_LOCAL, 0);
	    curbuf->b_p_vts_array = new_vts_array;
	    vim_free(old_vts_ary);
	}
	else
	{
	    // 'vartabstop' wasn't in use and a single value was given to
	    // retab then update 'tabstop'.
	    curbuf->b_p_ts = tabstop_first(new_vts_array);
	    vim_free(new_vts_array);
	}
	vim_free(new_ts_str);
    }
#else
    curbuf->b_p_ts = new_ts;
#endif
    coladvance(curwin->w_curswant);

    u_clearline();
}

patch 8.2.4359: crash when repeatedly using :retab

Problem:    crash when repeatedly using :retab.
Solution:   Bail out when the line is getting too long.


ex_retab(exarg_T *eap)
{
    linenr_T	lnum;
    int		got_tab = FALSE;
    long	num_spaces = 0;
    long	num_tabs;
    long	len;
    long	col;
    long	vcol;
    long	start_col = 0;		// For start of white-space string
    long	start_vcol = 0;		// For start of white-space string
    long	old_len;
    char_u	*ptr;
    char_u	*new_line = (char_u *)1; // init to non-NULL
    int		did_undo;		// called u_save for current line
#ifdef FEAT_VARTABS
    int		*new_vts_array = NULL;
    char_u	*new_ts_str;		// string value of tab argument
#else
    int		temp;
    int		new_ts;
#endif
    int		save_list;
    linenr_T	first_line = 0;		// first changed line
    linenr_T	last_line = 0;		// last changed line

    save_list = curwin->w_p_list;
    curwin->w_p_list = 0;	    // don't want list mode here

#ifdef FEAT_VARTABS
    new_ts_str = eap->arg;
    if (tabstop_set(eap->arg, &new_vts_array) == FAIL)
	return;
    while (vim_isdigit(*(eap->arg)) || *(eap->arg) == ',')
	++(eap->arg);

    // This ensures that either new_vts_array and new_ts_str are freshly
    // allocated, or new_vts_array points to an existing array and new_ts_str
    // is null.
    if (new_vts_array == NULL)
    {
	new_vts_array = curbuf->b_p_vts_array;
	new_ts_str = NULL;
    }
    else
	new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str);
#else
    ptr = eap->arg;
    new_ts = getdigits(&ptr);
    if (new_ts < 0 && *eap->arg == '-')
    {
	emsg(_(e_argument_must_be_positive));
	return;
    }
    if (new_ts < 0 || new_ts > TABSTOP_MAX)
    {
	semsg(_(e_invalid_argument_str), eap->arg);
	return;
    }
    if (new_ts == 0)
	new_ts = curbuf->b_p_ts;
#endif
    for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum)
    {
	ptr = ml_get(lnum);
	col = 0;
	vcol = 0;
	did_undo = FALSE;
	for (;;)
	{
	    if (VIM_ISWHITE(ptr[col]))
	    {
		if (!got_tab && num_spaces == 0)
		{
		    // First consecutive white-space
		    start_vcol = vcol;
		    start_col = col;
		}
		if (ptr[col] == ' ')
		    num_spaces++;
		else
		    got_tab = TRUE;
	    }
	    else
	    {
		if (got_tab || (eap->forceit && num_spaces > 1))
		{
		    // Retabulate this string of white-space

		    // len is virtual length of white string
		    len = num_spaces = vcol - start_vcol;
		    num_tabs = 0;
		    if (!curbuf->b_p_et)
		    {
#ifdef FEAT_VARTABS
			int t, s;

			tabstop_fromto(start_vcol, vcol,
					curbuf->b_p_ts, new_vts_array, &t, &s);
			num_tabs = t;
			num_spaces = s;
#else
			temp = new_ts - (start_vcol % new_ts);
			if (num_spaces >= temp)
			{
			    num_spaces -= temp;
			    num_tabs++;
			}
			num_tabs += num_spaces / new_ts;
			num_spaces -= (num_spaces / new_ts) * new_ts;
#endif
		    }
		    if (curbuf->b_p_et || got_tab ||
					(num_spaces + num_tabs < len))
		    {
			if (did_undo == FALSE)
			{
			    did_undo = TRUE;
			    if (u_save((linenr_T)(lnum - 1),
						(linenr_T)(lnum + 1)) == FAIL)
			    {
				new_line = NULL;	// flag out-of-memory
				break;
			    }
			}

			// len is actual number of white characters used
			len = num_spaces + num_tabs;
			old_len = (long)STRLEN(ptr);
			new_line = alloc(old_len - col + start_col + len + 1);
			if (new_line == NULL)
			    break;
			if (start_col > 0)
			    mch_memmove(new_line, ptr, (size_t)start_col);
			mch_memmove(new_line + start_col + len,
				      ptr + col, (size_t)(old_len - col + 1));
			ptr = new_line + start_col;
			for (col = 0; col < len; col++)
			    ptr[col] = (col < num_tabs) ? '\t' : ' ';
			if (ml_replace(lnum, new_line, FALSE) == OK)
			    // "new_line" may have been copied
			    new_line = curbuf->b_ml.ml_line_ptr;
			if (first_line == 0)
			    first_line = lnum;
			last_line = lnum;
			ptr = new_line;
			col = start_col + len;
		    }
		}
		got_tab = FALSE;
		num_spaces = 0;
	    }
	    if (ptr[col] == NUL)
		break;
	    vcol += chartabsize(ptr + col, (colnr_T)vcol);
	    if (vcol >= MAXCOL)
	    {
		emsg(_(e_resulting_text_too_long));
		break;
	    }
	    if (has_mbyte)
		col += (*mb_ptr2len)(ptr + col);
	    else
		++col;
	}
	if (new_line == NULL)		    // out of memory
	    break;
	line_breakcheck();
    }
    if (got_int)
	emsg(_(e_interrupted));

#ifdef FEAT_VARTABS
    // If a single value was given then it can be considered equal to
    // either the value of 'tabstop' or the value of 'vartabstop'.
    if (tabstop_count(curbuf->b_p_vts_array) == 0
	&& tabstop_count(new_vts_array) == 1
	&& curbuf->b_p_ts == tabstop_first(new_vts_array))
	; // not changed
    else if (tabstop_count(curbuf->b_p_vts_array) > 0
        && tabstop_eq(curbuf->b_p_vts_array, new_vts_array))
	; // not changed
    else
	redraw_curbuf_later(NOT_VALID);
#else
    if (curbuf->b_p_ts != new_ts)
	redraw_curbuf_later(NOT_VALID);
#endif
    if (first_line != 0)
	changed_lines(first_line, 0, last_line + 1, 0L);

    curwin->w_p_list = save_list;	// restore 'list'

#ifdef FEAT_VARTABS
    if (new_ts_str != NULL)		// set the new tabstop
    {
	// If 'vartabstop' is in use or if the value given to retab has more
	// than one tabstop then update 'vartabstop'.
	int *old_vts_ary = curbuf->b_p_vts_array;

	if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1)
	{
	    set_string_option_direct((char_u *)"vts", -1, new_ts_str,
							OPT_FREE|OPT_LOCAL, 0);
	    curbuf->b_p_vts_array = new_vts_array;
	    vim_free(old_vts_ary);
	}
	else
	{
	    // 'vartabstop' wasn't in use and a single value was given to
	    // retab then update 'tabstop'.
	    curbuf->b_p_ts = tabstop_first(new_vts_array);
	    vim_free(new_vts_array);
	}
	vim_free(new_ts_str);
    }
#else
    curbuf->b_p_ts = new_ts;
#endif
    coladvance(curwin->w_curswant);

    u_clearline();
}


mp_sint32 LoaderXM::load(XMFileBase& f, XModule* module)
{
	mp_ubyte insData[230];
	mp_sint32 smpReloc[MP_MAXINSSAMPS];
	mp_ubyte nbu[MP_MAXINSSAMPS];
	mp_uint32 fileSize = 0;

	module->cleanUp();

	// this will make code much easier to read
	TXMHeader*		header = &module->header;
	TXMInstrument*	instr  = module->instr;
	TXMSample*		smp	   = module->smp;
	TXMPattern*		phead  = module->phead;

	// we're already out of memory here
	if (!phead || !instr || !smp)
		return MP_OUT_OF_MEMORY;

	fileSize = f.sizeWithBaseOffset();

	f.read(&header->sig,1,17);
	f.read(&header->name,1,20);
	f.read(&header->whythis1a,1,1);
	header->whythis1a=0;
	f.read(&header->tracker,1,20);
	f.readWords(&header->ver,1);

	if (header->ver != 0x102 &&
		header->ver != 0x103 && // untested
		header->ver != 0x104)
		return MP_LOADER_FAILED;

	f.readDwords(&header->hdrsize,1);

	header->hdrsize-=4;

	mp_uint32 hdrSize = 0x110;
	if (header->hdrsize > hdrSize)
		hdrSize = header->hdrsize;

	mp_ubyte* hdrBuff = new mp_ubyte[hdrSize];
	memset(hdrBuff, 0, hdrSize);

	f.read(hdrBuff, 1, header->hdrsize);

	header->ordnum = LittleEndian::GET_WORD(hdrBuff);
	header->restart = LittleEndian::GET_WORD(hdrBuff+2);
	header->channum = LittleEndian::GET_WORD(hdrBuff+4);
	header->patnum = LittleEndian::GET_WORD(hdrBuff+6);
	header->insnum = LittleEndian::GET_WORD(hdrBuff+8);
	header->freqtab = LittleEndian::GET_WORD(hdrBuff+10);
	header->tempo = LittleEndian::GET_WORD(hdrBuff+12);
	header->speed = LittleEndian::GET_WORD(hdrBuff+14);
	memcpy(header->ord, hdrBuff+16, 256);
	if(header->ordnum > MP_MAXORDERS)
		header->ordnum = MP_MAXORDERS;
	if(header->insnum > MP_MAXINS)
		return MP_LOADER_FAILED;

	delete[] hdrBuff;

	header->mainvol=255;
	header->flags = XModule::MODULE_XMNOTECLIPPING |
		XModule::MODULE_XMARPEGGIO |
		XModule::MODULE_XMPORTANOTEBUFFER |
		XModule::MODULE_XMVOLCOLUMNVIBRATO;

	header->uppernotebound = 119;

	mp_sint32 i,y,sc;
	for (i=0;i<32;i++) header->pan[i]=0x80;

	// old version?
	if (header->ver == 0x102 || header->ver == 0x103)
	{
		mp_sint32 s = 0;
		mp_sint32 e = 0;
		for (y=0;y<header->insnum;y++) {

			f.readDwords(&instr[y].size,1);
			f.read(&instr[y].name,1,22);
			f.read(&instr[y].type,1,1);
			mp_uword numSamples = 0;
			f.readWords(&numSamples,1);
			if(numSamples > MP_MAXINSSAMPS)
				return MP_LOADER_FAILED;
			instr[y].samp = numSamples;

			if (instr[y].size == 29)
			{
#ifdef MILKYTRACKER
				s+=16;
#endif
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
				continue;
			}

			f.readDwords(&instr[y].shsize,1);

			memset(insData, 0, 230);

			if (instr[y].size - 33 > 230)
				return MP_OUT_OF_MEMORY;

			f.read(insData, 1, instr[y].size - 33);

			if (instr[y].samp) {
				mp_ubyte* insDataPtr = insData;

				memcpy(nbu, insDataPtr, MP_MAXINSSAMPS);
				insDataPtr+=MP_MAXINSSAMPS;

				TEnvelope venv;
				TEnvelope penv;
				memset(&venv,0,sizeof(venv));
				memset(&penv,0,sizeof(penv));

				mp_sint32 k;
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					venv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					venv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					penv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					penv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}

				venv.num = *insDataPtr++;
				if (venv.num > XM_ENVELOPENUMPOINTS) venv.num = XM_ENVELOPENUMPOINTS;
				penv.num = *insDataPtr++;
				if (penv.num > XM_ENVELOPENUMPOINTS) penv.num = XM_ENVELOPENUMPOINTS;
				venv.sustain = *insDataPtr++;
				venv.loops = *insDataPtr++;
				venv.loope = *insDataPtr++;
				penv.sustain = *insDataPtr++;
				penv.loops = *insDataPtr++;
				penv.loope = *insDataPtr++;
				venv.type = *insDataPtr++;
				penv.type = *insDataPtr++;

				mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
				mp_uword volfade;

				vibtype = *insDataPtr++;
				vibsweep = *insDataPtr++;
				vibdepth = *insDataPtr++;
				vibrate = *insDataPtr++;

				vibdepth<<=1;

				volfade = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;
				volfade<<=1;

				//instr[y].res = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;

				for (mp_sint32 l=0;l<XM_ENVELOPENUMPOINTS;l++) {
					venv.env[l][1]<<=2;
					penv.env[l][1]<<=2;
				}

				if (!module->addVolumeEnvelope(venv))
					return MP_OUT_OF_MEMORY;
				if (!module->addPanningEnvelope(penv))
					return MP_OUT_OF_MEMORY;

				mp_sint32 g=0, sc;
				for (sc=0;sc<instr[y].samp;sc++) {

					smp[g+s].flags=3;
					smp[g+s].venvnum=e+1;
					smp[g+s].penvnum=e+1;

					smp[g+s].vibtype=vibtype;
					smp[g+s].vibsweep=vibsweep;
					smp[g+s].vibdepth=vibdepth;
					smp[g+s].vibrate=vibrate;
					smp[g+s].volfade=volfade;

					// not sure why I did that, actually doesn't make sense
					//if (!(venv.type&1)) smp[g+s].volfade=0;

					f.readDwords(&smp[g+s].samplen,1);
					f.readDwords(&smp[g+s].loopstart,1);
					f.readDwords(&smp[g+s].looplen,1);
					smp[g+s].vol=XModule::vol64to255(f.readByte());
					//f.read(&smp[g+s].vol,1,1);
					f.read(&smp[g+s].finetune,1,1);
					f.read(&smp[g+s].type,1,1);
#ifdef VERBOSE
					printf("Before: %i, After: %i\n", smp[g+s].type, smp[g+s].type & (3+16));
#endif
					f.read(&smp[g+s].pan,1,1);
					f.read(&smp[g+s].relnote,1,1);
					f.read(&smp[g+s].res,1,1);
					f.read(&smp[g+s].name,1,22);

					char line[30];
					memset(line, 0, sizeof(line));
					XModule::convertStr(line, smp[g+s].name, 23, false);
					if (line[0])
						module->addSongMessageLine(line);

					// ignore empty samples
#ifndef MILKYTRACKER
					// ignore empty samples when not being a tracker
					if (smp[g+s].samplen) {
						smpReloc[sc] = g;
						g++;
					}
					else
						smpReloc[sc] = -1;
#else
					smpReloc[sc] = g;
					g++;
#endif
				}

				instr[y].samp = g;

				for (sc = 0; sc < MP_MAXINSSAMPS; sc++) {
					if (smpReloc[nbu[sc]] == -1)
						instr[y].snum[sc] = -1;
					else
						instr[y].snum[sc] = smpReloc[nbu[sc]]+s;
				}

				e++;

			}
			else
			{
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
			}

#ifdef MILKYTRACKER
			s+=16;
#else
			s+=instr[y].samp;
#endif

		}

		header->smpnum=s;
		header->volenvnum=e;
		header->panenvnum=e;
	}

	for (y=0;y<header->patnum;y++) {

		if (header->ver == 0x104 || header->ver == 0x103)
		{
			f.readDwords(&phead[y].len,1);
			f.read(&phead[y].ptype,1,1);
			f.readWords(&phead[y].rows,1);
			f.readWords(&phead[y].patdata,1);
		}
		else
		{
			f.readDwords(&phead[y].len,1);
			f.read(&phead[y].ptype,1,1);
			phead[y].rows = (mp_uword)f.readByte()+1;
			f.readWords(&phead[y].patdata,1);
		}

		phead[y].effnum=2;
		phead[y].channum=(mp_ubyte)header->channum;

		phead[y].patternData = new mp_ubyte[phead[y].rows*header->channum*6];

		// out of memory?
		if (phead[y].patternData == NULL)
		{
			return MP_OUT_OF_MEMORY;
		}

		memset(phead[y].patternData,0,phead[y].rows*header->channum*6);

		if (phead[y].patdata) {
			mp_ubyte *buffer = new mp_ubyte[phead[y].patdata];

			// out of memory?
			if (buffer == NULL)
			{
				return MP_OUT_OF_MEMORY;
			}

			f.read(buffer,1,phead[y].patdata);

			//printf("%i\n", phead[y].patdata);

			mp_sint32 pc = 0, bc = 0;
			for (mp_sint32 r=0;r<phead[y].rows;r++) {
				for (mp_sint32 c=0;c<header->channum;c++) {

					mp_ubyte slot[5];
					memset(slot,0,5);

					if ((buffer[pc]&128)) {

						mp_ubyte pb = buffer[pc];
						pc++;

						if ((pb&1)) {
							//phead[y].patternData[bc]=buffer[pc];
							slot[0]=buffer[pc];
							pc++;
						}
						if ((pb&2)) {
							//phead[y].patternData[bc+1]=buffer[pc];
							slot[1]=buffer[pc];
							pc++;
						}
						if ((pb&4)) {
							//phead[y].patternData[bc+2]=buffer[pc];
							slot[2]=buffer[pc];
							pc++;
						}
						if ((pb&8)) {
							//phead[y].patternData[bc+3]=buffer[pc];
							slot[3]=buffer[pc];
							pc++;
						}
						if ((pb&16)) {
							//phead[y].patternData[bc+4]=buffer[pc];
							slot[4]=buffer[pc];
							pc++;
						}

					}
					else {
						//memcpy(phead[y].patternData+bc,buffer+pc,5);
						memcpy(slot,buffer+pc,5);
						pc+=5;
					}

					char gl=0;
					for (mp_sint32 i=0;i<XModule::numValidXMEffects;i++)
						if (slot[3]==XModule::validXMEffects[i]) gl=1;

					if (!gl) slot[3]=slot[4]=0;

					if ((slot[3]==0xC)||(slot[3]==0x10)) {
						slot[4] = XModule::vol64to255(slot[4]);
						/*mp_sint32 bl = slot[4];
						if (bl>64) bl=64;
						slot[4]=(bl*261120)>>16;*/
					}

					if ((!slot[3])&&(slot[4])) slot[3]=0x20;

					if (slot[3]==0xE) {
						slot[3]=(slot[4]>>4)+0x30;
						slot[4]=slot[4]&0xf;
					}

					if (slot[3]==0x21) {
						slot[3]=(slot[4]>>4)+0x40;
						slot[4]=slot[4]&0xf;
					}

					if (slot[0]==97) slot[0]=XModule::NOTE_OFF;

					phead[y].patternData[bc]=slot[0];
					phead[y].patternData[bc+1]=slot[1];

					XModule::convertXMVolumeEffects(slot[2], phead[y].patternData[bc+2], phead[y].patternData[bc+3]);

					phead[y].patternData[bc+4]=slot[3];
					phead[y].patternData[bc+5]=slot[4];

					/*if ((y==3)&&(c==2)) {
						for (mp_sint32 bl=0;bl<6;bl++) cprintf("%x ",phead[y].patternData[bc+bl]);
					cprintf("\r\n");
					getch();
					};*/

					/*printf("Note : %i\r\n",phead[y].patternData[bc]);
					printf("Ins  : %i\r\n",phead[y].patternData[bc+1]);
					printf("Vol  : %i\r\n",phead[y].patternData[bc+2]);
					printf("Eff  : %i\r\n",phead[y].patternData[bc+3]);
					printf("Effop: %i\r\n",phead[y].patternData[bc+4]);
					getch();*/

					bc+=6;
				} // for c

			} // for r

			delete[] buffer;
		}

	}

	if (header->ver == 0x104)
	{
		mp_sint32 s = 0;
		mp_sint32 e = 0;
		for (y=0;y<header->insnum;y++) {

			// fixes MOOH.XM loading problems
			// seems to store more instruments in the header than in the actual file
			if (f.posWithBaseOffset() >= fileSize)
				break;

			//TXMInstrument* ins = &instr[y];

			f.readDwords(&instr[y].size,1);

			if (instr[y].size < 29)
			{
				mp_ubyte buffer[29];
				memset(buffer, 0, sizeof(buffer));
				f.read(buffer, 1, instr[y].size - 4);
				memcpy(instr[y].name, buffer, 22);
				instr[y].type = buffer[22];
				instr[y].samp = LittleEndian::GET_WORD(buffer + 23);
			}
			else
			{
				f.read(&instr[y].name,1,22);
				f.read(&instr[y].type,1,1);
				f.readWords(&instr[y].samp,1);
			}
			if (instr[y].samp > MP_MAXINSSAMPS)
				return MP_LOADER_FAILED;

			//printf("%i, %i\n", instr[y].size, instr[y].samp);

			if (instr[y].size <= 29)
			{
#ifdef MILKYTRACKER
				s+=16;
#endif
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
				continue;
			}

			f.readDwords(&instr[y].shsize,1);
#ifdef VERBOSE
			printf("%i/%i: %i, %i, %i, %s\n",y,header->insnum-1,instr[y].size,instr[y].shsize,instr[y].samp,instr[y].name);
#endif
			memset(insData, 0, 230);

			if (instr[y].size - 33 > 230)
			{
				//return -7;
				break;
			}

			f.read(insData, 1, instr[y].size - 33);

			/*printf("%i\r\n",instr[y].size);
			printf("%s\r\n",instr[y].name);
			printf("%i\r\n",instr[y].type);
			printf("%i\r\n",instr[y].samp);
			printf("%i\r\n",instr[y].shsize);*/
			//getch();

			memset(smpReloc, 0, sizeof(smpReloc));

			if (instr[y].samp) {
				mp_ubyte* insDataPtr = insData;

				//f.read(&nbu,1,96);

				memcpy(nbu, insDataPtr, MP_MAXINSSAMPS);
				insDataPtr+=MP_MAXINSSAMPS;

				TEnvelope venv;
				TEnvelope penv;
				memset(&venv,0,sizeof(venv));
				memset(&penv,0,sizeof(penv));

				mp_sint32 k;
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					venv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					venv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					penv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					penv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}

				venv.num = *insDataPtr++;
				if (venv.num > XM_ENVELOPENUMPOINTS) venv.num = XM_ENVELOPENUMPOINTS;
				penv.num = *insDataPtr++;
				if (penv.num > XM_ENVELOPENUMPOINTS) penv.num = XM_ENVELOPENUMPOINTS;
				venv.sustain = *insDataPtr++;
				venv.loops = *insDataPtr++;
				venv.loope = *insDataPtr++;
				penv.sustain = *insDataPtr++;
				penv.loops = *insDataPtr++;
				penv.loope = *insDataPtr++;
				venv.type = *insDataPtr++;
				penv.type = *insDataPtr++;

				mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
				mp_uword volfade;

				vibtype = *insDataPtr++;
				vibsweep = *insDataPtr++;
				vibdepth = *insDataPtr++;
				vibrate = *insDataPtr++;

				vibdepth<<=1;

				//f.readWords(&volfade,1);
				volfade = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;
				volfade<<=1;

				//instr[y].res = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;

				for (mp_sint32 l=0;l<XM_ENVELOPENUMPOINTS;l++) {
					venv.env[l][1]<<=2;
					penv.env[l][1]<<=2;
				}

				if (!module->addVolumeEnvelope(venv))
					return MP_OUT_OF_MEMORY;
				if (!module->addPanningEnvelope(penv))
					return MP_OUT_OF_MEMORY;

				mp_sint32 g=0, sc;
				for (sc=0;sc<instr[y].samp;sc++) {
					//TXMSample* smpl = &smp[g+s];

					smp[g+s].flags=3;
					smp[g+s].venvnum=e+1;
					smp[g+s].penvnum=e+1;

					smp[g+s].vibtype=vibtype;
					smp[g+s].vibsweep=vibsweep;
					smp[g+s].vibdepth=vibdepth;
					smp[g+s].vibrate=vibrate;
					smp[g+s].volfade=volfade;

					// not sure why I did that, actually doesn't make sense
					//if (!(venv.type&1)) smp[g+s].volfade=0;

					f.readDwords(&smp[g+s].samplen,1);

					f.readDwords(&smp[g+s].loopstart,1);
					f.readDwords(&smp[g+s].looplen,1);
					smp[g+s].vol=XModule::vol64to255(f.readByte());
					//f.read(&smp[g+s].vol,1,1);
					f.read(&smp[g+s].finetune,1,1);
					f.read(&smp[g+s].type,1,1);
#ifdef VERBOSE
					printf("Before: %i, After: %i\n", smp[g+s].type, smp[g+s].type & (3+16));
#endif
					f.read(&smp[g+s].pan,1,1);
					f.read(&smp[g+s].relnote,1,1);
					f.read(&smp[g+s].res,1,1);
					f.read(&smp[g+s].name,1,22);

					char line[30];
					memset(line, 0, sizeof(line));
					XModule::convertStr(line, smp[g+s].name, 23, false);
					if (line[0])
						module->addSongMessageLine(line);

#ifndef MILKYTRACKER
					// ignore empty samples when not being a tracker
					if (smp[g+s].samplen) {
						smpReloc[sc] = g;
						g++;
					}
					else
						smpReloc[sc] = -1;
#else
					smpReloc[sc] = g;
					g++;
#endif
				}

				instr[y].samp = g;

				for (sc = 0; sc < MP_MAXINSSAMPS; sc++) {
					if (smpReloc[nbu[sc]] == -1)
						instr[y].snum[sc] = -1;
					else
						instr[y].snum[sc] = smpReloc[nbu[sc]]+s;
				}

				for (sc=0;sc<instr[y].samp;sc++) {

					if (smp[s].samplen)
					{
						bool adpcm = (smp[s].res == 0xAD);

						mp_uint32 oldSize = smp[s].samplen;
						if (smp[s].type&16)
						{
							smp[s].samplen>>=1;
							smp[s].loopstart>>=1;
							smp[s].looplen>>=1;
						}

						mp_sint32 result = module->loadModuleSample(f, s,
													 adpcm ? XModule::ST_PACKING_ADPCM : XModule::ST_DELTA,
													 adpcm ? (XModule::ST_PACKING_ADPCM | XModule::ST_16BIT) : (XModule::ST_DELTA | XModule::ST_16BIT),
													 oldSize);
						if (result != MP_OK)
							return result;

						if (adpcm)
							smp[s].res = 0;
					}

					s++;

					if (s>=MP_MAXSAMPLES)
						return MP_OUT_OF_MEMORY;

				}

				e++;

			}
			else
			{
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
			}

#ifdef MILKYTRACKER
			s+=16 - instr[y].samp;
#endif

		}

		header->smpnum=s;
		header->volenvnum=e;
		header->panenvnum=e;

	}
	else
	{
		mp_sint32 s = 0;
		for (y=0;y<header->insnum;y++) {
			for (sc=0;sc<instr[y].samp;sc++) {

				if (smp[s].samplen)
				{
					mp_uint32 oldSize = smp[s].samplen;
					if (smp[s].type&16)
					{
						smp[s].samplen>>=1;
						smp[s].loopstart>>=1;
						smp[s].looplen>>=1;
					}

					mp_sint32 result = module->loadModuleSample(f, s, XModule::ST_DELTA, XModule::ST_DELTA | XModule::ST_16BIT, oldSize);
					if (result != MP_OK)
						return result;
				}

				s++;

				if (s>=MP_MAXSAMPLES)
					return MP_OUT_OF_MEMORY;
			}

#ifdef MILKYTRACKER
			s+=16 - instr[y].samp;
#endif

		}
	}

	// convert modplug stereo samples
	for (mp_sint32 s = 0; s < header->smpnum; s++)
	{
		if (smp[s].type & 32)
		{
			// that's what's allowed, stupid modplug tracker
			smp[s].type &= 3+16;

			if (smp[s].sample == NULL)
				continue;

			if (!(smp[s].type&16)) {
				smp[s].samplen>>=1;
				smp[s].loopstart>>=1;
				smp[s].looplen>>=1;

				mp_sbyte* sample = (mp_sbyte*)smp[s].sample;
				mp_sint32 samplen = smp[s].samplen;
				for (mp_sint32 i = 0; i < samplen; i++)
				{
					mp_sint32 s = ((mp_sint32)sample[i] + (mp_sint32)sample[i + samplen]) >> 1;
					if (s < -128) s = -128;
					if (s > 127) s = 127;
					sample[i] = (mp_sbyte)s;
				}
			}
			else
			{
				smp[s].samplen>>=1;
				smp[s].loopstart>>=1;
				smp[s].looplen>>=1;

				mp_sword* sample = (mp_sword*)smp[s].sample;
				mp_sint32 samplen = smp[s].samplen;
				for (mp_sint32 i = 0; i < samplen; i++)
				{
					mp_sint32 s = ((mp_sint32)sample[i] + (mp_sint32)sample[i + samplen]) >> 1;
					if (s < -32768) s = -32768;
					if (s > 32767) s = 32767;
					sample[i] = (mp_sword)s;
				}
			}
		}

		// correct loop type 0x03 (undefined)
		// will become ping pong loop
		// note that FT2 will refuse to load XM files with such a loop type
		if ((smp[s].type & 0x3) == 0x3)
			smp[s].type&=~1;
	}

	// correct number of patterns if necessary, otherwise the post processing will remove
	// the "invalid" patterns from the order list
	bool addPatterns = false;
	for (i = 0; i < header->ordnum; i++)
		if (header->ord[i]+1 > header->patnum)
		{
			header->patnum = header->ord[i]+1;
			addPatterns = true;
		}

	// if the pattern number has been adjusted, add some empty patterns
	if (addPatterns)
	{
		for (i = 0; i < header->patnum; i++)
			if (phead[i].patternData == NULL)
			{
				phead[i].rows = 64;
				phead[i].effnum = 2;
				phead[i].channum = (mp_ubyte)header->channum;

				phead[i].patternData = new mp_ubyte[phead[i].rows*header->channum*6];

				// out of memory?
				if (phead[i].patternData == NULL)
				{
					return MP_OUT_OF_MEMORY;
				}

				memset(phead[i].patternData,0,phead[i].rows*header->channum*6);
			}
	}

	// check for MODPLUG extensions
	if (f.posWithBaseOffset() + 8 <= fileSize)
	{
		char buffer[4];
		f.read(buffer, 1, 4);
		if (memcmp(buffer, "text", 4) == 0)
		{
			mp_uint32 len = f.readDword();
			module->allocateSongMessage(len+1);

			memset(module->message, 0, len+1);

			f.read(module->message, 1, len);
		}
	}

	module->postProcessSamples();

	return MP_OK;
}

Fix possible stack corruption with XM instrument headers claiming a size of less than 4

Closes #275


mp_sint32 LoaderXM::load(XMFileBase& f, XModule* module)
{
	mp_ubyte insData[230];
	mp_sint32 smpReloc[MP_MAXINSSAMPS];
	mp_ubyte nbu[MP_MAXINSSAMPS];
	mp_uint32 fileSize = 0;

	module->cleanUp();

	// this will make code much easier to read
	TXMHeader*		header = &module->header;
	TXMInstrument*	instr  = module->instr;
	TXMSample*		smp	   = module->smp;
	TXMPattern*		phead  = module->phead;

	// we're already out of memory here
	if (!phead || !instr || !smp)
		return MP_OUT_OF_MEMORY;

	fileSize = f.sizeWithBaseOffset();

	f.read(&header->sig,1,17);
	f.read(&header->name,1,20);
	f.read(&header->whythis1a,1,1);
	header->whythis1a=0;
	f.read(&header->tracker,1,20);
	f.readWords(&header->ver,1);

	if (header->ver != 0x102 &&
		header->ver != 0x103 && // untested
		header->ver != 0x104)
		return MP_LOADER_FAILED;

	f.readDwords(&header->hdrsize,1);

	header->hdrsize-=4;

	mp_uint32 hdrSize = 0x110;
	if (header->hdrsize > hdrSize)
		hdrSize = header->hdrsize;

	mp_ubyte* hdrBuff = new mp_ubyte[hdrSize];
	memset(hdrBuff, 0, hdrSize);

	f.read(hdrBuff, 1, header->hdrsize);

	header->ordnum = LittleEndian::GET_WORD(hdrBuff);
	header->restart = LittleEndian::GET_WORD(hdrBuff+2);
	header->channum = LittleEndian::GET_WORD(hdrBuff+4);
	header->patnum = LittleEndian::GET_WORD(hdrBuff+6);
	header->insnum = LittleEndian::GET_WORD(hdrBuff+8);
	header->freqtab = LittleEndian::GET_WORD(hdrBuff+10);
	header->tempo = LittleEndian::GET_WORD(hdrBuff+12);
	header->speed = LittleEndian::GET_WORD(hdrBuff+14);
	memcpy(header->ord, hdrBuff+16, 256);
	if(header->ordnum > MP_MAXORDERS)
		header->ordnum = MP_MAXORDERS;
	if(header->insnum > MP_MAXINS)
		return MP_LOADER_FAILED;

	delete[] hdrBuff;

	header->mainvol=255;
	header->flags = XModule::MODULE_XMNOTECLIPPING |
		XModule::MODULE_XMARPEGGIO |
		XModule::MODULE_XMPORTANOTEBUFFER |
		XModule::MODULE_XMVOLCOLUMNVIBRATO;

	header->uppernotebound = 119;

	mp_sint32 i,y,sc;
	for (i=0;i<32;i++) header->pan[i]=0x80;

	// old version?
	if (header->ver == 0x102 || header->ver == 0x103)
	{
		mp_sint32 s = 0;
		mp_sint32 e = 0;
		for (y=0;y<header->insnum;y++) {

			f.readDwords(&instr[y].size,1);
			f.read(&instr[y].name,1,22);
			f.read(&instr[y].type,1,1);
			mp_uword numSamples = 0;
			f.readWords(&numSamples,1);
			if(numSamples > MP_MAXINSSAMPS)
				return MP_LOADER_FAILED;
			instr[y].samp = numSamples;

			if (instr[y].size == 29)
			{
#ifdef MILKYTRACKER
				s+=16;
#endif
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
				continue;
			}

			f.readDwords(&instr[y].shsize,1);

			memset(insData, 0, 230);

			if (instr[y].size - 33 > 230)
				return MP_OUT_OF_MEMORY;

			f.read(insData, 1, instr[y].size - 33);

			if (instr[y].samp) {
				mp_ubyte* insDataPtr = insData;

				memcpy(nbu, insDataPtr, MP_MAXINSSAMPS);
				insDataPtr+=MP_MAXINSSAMPS;

				TEnvelope venv;
				TEnvelope penv;
				memset(&venv,0,sizeof(venv));
				memset(&penv,0,sizeof(penv));

				mp_sint32 k;
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					venv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					venv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					penv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					penv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}

				venv.num = *insDataPtr++;
				if (venv.num > XM_ENVELOPENUMPOINTS) venv.num = XM_ENVELOPENUMPOINTS;
				penv.num = *insDataPtr++;
				if (penv.num > XM_ENVELOPENUMPOINTS) penv.num = XM_ENVELOPENUMPOINTS;
				venv.sustain = *insDataPtr++;
				venv.loops = *insDataPtr++;
				venv.loope = *insDataPtr++;
				penv.sustain = *insDataPtr++;
				penv.loops = *insDataPtr++;
				penv.loope = *insDataPtr++;
				venv.type = *insDataPtr++;
				penv.type = *insDataPtr++;

				mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
				mp_uword volfade;

				vibtype = *insDataPtr++;
				vibsweep = *insDataPtr++;
				vibdepth = *insDataPtr++;
				vibrate = *insDataPtr++;

				vibdepth<<=1;

				volfade = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;
				volfade<<=1;

				//instr[y].res = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;

				for (mp_sint32 l=0;l<XM_ENVELOPENUMPOINTS;l++) {
					venv.env[l][1]<<=2;
					penv.env[l][1]<<=2;
				}

				if (!module->addVolumeEnvelope(venv))
					return MP_OUT_OF_MEMORY;
				if (!module->addPanningEnvelope(penv))
					return MP_OUT_OF_MEMORY;

				mp_sint32 g=0, sc;
				for (sc=0;sc<instr[y].samp;sc++) {

					smp[g+s].flags=3;
					smp[g+s].venvnum=e+1;
					smp[g+s].penvnum=e+1;

					smp[g+s].vibtype=vibtype;
					smp[g+s].vibsweep=vibsweep;
					smp[g+s].vibdepth=vibdepth;
					smp[g+s].vibrate=vibrate;
					smp[g+s].volfade=volfade;

					// not sure why I did that, actually doesn't make sense
					//if (!(venv.type&1)) smp[g+s].volfade=0;

					f.readDwords(&smp[g+s].samplen,1);
					f.readDwords(&smp[g+s].loopstart,1);
					f.readDwords(&smp[g+s].looplen,1);
					smp[g+s].vol=XModule::vol64to255(f.readByte());
					//f.read(&smp[g+s].vol,1,1);
					f.read(&smp[g+s].finetune,1,1);
					f.read(&smp[g+s].type,1,1);
#ifdef VERBOSE
					printf("Before: %i, After: %i\n", smp[g+s].type, smp[g+s].type & (3+16));
#endif
					f.read(&smp[g+s].pan,1,1);
					f.read(&smp[g+s].relnote,1,1);
					f.read(&smp[g+s].res,1,1);
					f.read(&smp[g+s].name,1,22);

					char line[30];
					memset(line, 0, sizeof(line));
					XModule::convertStr(line, smp[g+s].name, 23, false);
					if (line[0])
						module->addSongMessageLine(line);

					// ignore empty samples
#ifndef MILKYTRACKER
					// ignore empty samples when not being a tracker
					if (smp[g+s].samplen) {
						smpReloc[sc] = g;
						g++;
					}
					else
						smpReloc[sc] = -1;
#else
					smpReloc[sc] = g;
					g++;
#endif
				}

				instr[y].samp = g;

				for (sc = 0; sc < MP_MAXINSSAMPS; sc++) {
					if (smpReloc[nbu[sc]] == -1)
						instr[y].snum[sc] = -1;
					else
						instr[y].snum[sc] = smpReloc[nbu[sc]]+s;
				}

				e++;

			}
			else
			{
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
			}

#ifdef MILKYTRACKER
			s+=16;
#else
			s+=instr[y].samp;
#endif

		}

		header->smpnum=s;
		header->volenvnum=e;
		header->panenvnum=e;
	}

	for (y=0;y<header->patnum;y++) {

		if (header->ver == 0x104 || header->ver == 0x103)
		{
			f.readDwords(&phead[y].len,1);
			f.read(&phead[y].ptype,1,1);
			f.readWords(&phead[y].rows,1);
			f.readWords(&phead[y].patdata,1);
		}
		else
		{
			f.readDwords(&phead[y].len,1);
			f.read(&phead[y].ptype,1,1);
			phead[y].rows = (mp_uword)f.readByte()+1;
			f.readWords(&phead[y].patdata,1);
		}

		phead[y].effnum=2;
		phead[y].channum=(mp_ubyte)header->channum;

		phead[y].patternData = new mp_ubyte[phead[y].rows*header->channum*6];

		// out of memory?
		if (phead[y].patternData == NULL)
		{
			return MP_OUT_OF_MEMORY;
		}

		memset(phead[y].patternData,0,phead[y].rows*header->channum*6);

		if (phead[y].patdata) {
			mp_ubyte *buffer = new mp_ubyte[phead[y].patdata];

			// out of memory?
			if (buffer == NULL)
			{
				return MP_OUT_OF_MEMORY;
			}

			f.read(buffer,1,phead[y].patdata);

			//printf("%i\n", phead[y].patdata);

			mp_sint32 pc = 0, bc = 0;
			for (mp_sint32 r=0;r<phead[y].rows;r++) {
				for (mp_sint32 c=0;c<header->channum;c++) {

					mp_ubyte slot[5];
					memset(slot,0,5);

					if ((buffer[pc]&128)) {

						mp_ubyte pb = buffer[pc];
						pc++;

						if ((pb&1)) {
							//phead[y].patternData[bc]=buffer[pc];
							slot[0]=buffer[pc];
							pc++;
						}
						if ((pb&2)) {
							//phead[y].patternData[bc+1]=buffer[pc];
							slot[1]=buffer[pc];
							pc++;
						}
						if ((pb&4)) {
							//phead[y].patternData[bc+2]=buffer[pc];
							slot[2]=buffer[pc];
							pc++;
						}
						if ((pb&8)) {
							//phead[y].patternData[bc+3]=buffer[pc];
							slot[3]=buffer[pc];
							pc++;
						}
						if ((pb&16)) {
							//phead[y].patternData[bc+4]=buffer[pc];
							slot[4]=buffer[pc];
							pc++;
						}

					}
					else {
						//memcpy(phead[y].patternData+bc,buffer+pc,5);
						memcpy(slot,buffer+pc,5);
						pc+=5;
					}

					char gl=0;
					for (mp_sint32 i=0;i<XModule::numValidXMEffects;i++)
						if (slot[3]==XModule::validXMEffects[i]) gl=1;

					if (!gl) slot[3]=slot[4]=0;

					if ((slot[3]==0xC)||(slot[3]==0x10)) {
						slot[4] = XModule::vol64to255(slot[4]);
						/*mp_sint32 bl = slot[4];
						if (bl>64) bl=64;
						slot[4]=(bl*261120)>>16;*/
					}

					if ((!slot[3])&&(slot[4])) slot[3]=0x20;

					if (slot[3]==0xE) {
						slot[3]=(slot[4]>>4)+0x30;
						slot[4]=slot[4]&0xf;
					}

					if (slot[3]==0x21) {
						slot[3]=(slot[4]>>4)+0x40;
						slot[4]=slot[4]&0xf;
					}

					if (slot[0]==97) slot[0]=XModule::NOTE_OFF;

					phead[y].patternData[bc]=slot[0];
					phead[y].patternData[bc+1]=slot[1];

					XModule::convertXMVolumeEffects(slot[2], phead[y].patternData[bc+2], phead[y].patternData[bc+3]);

					phead[y].patternData[bc+4]=slot[3];
					phead[y].patternData[bc+5]=slot[4];

					/*if ((y==3)&&(c==2)) {
						for (mp_sint32 bl=0;bl<6;bl++) cprintf("%x ",phead[y].patternData[bc+bl]);
					cprintf("\r\n");
					getch();
					};*/

					/*printf("Note : %i\r\n",phead[y].patternData[bc]);
					printf("Ins  : %i\r\n",phead[y].patternData[bc+1]);
					printf("Vol  : %i\r\n",phead[y].patternData[bc+2]);
					printf("Eff  : %i\r\n",phead[y].patternData[bc+3]);
					printf("Effop: %i\r\n",phead[y].patternData[bc+4]);
					getch();*/

					bc+=6;
				} // for c

			} // for r

			delete[] buffer;
		}

	}

	if (header->ver == 0x104)
	{
		mp_sint32 s = 0;
		mp_sint32 e = 0;
		for (y=0;y<header->insnum;y++) {

			// fixes MOOH.XM loading problems
			// seems to store more instruments in the header than in the actual file
			if (f.posWithBaseOffset() >= fileSize)
				break;

			//TXMInstrument* ins = &instr[y];

			f.readDwords(&instr[y].size,1);

			if (instr[y].size >= 4 && instr[y].size < 29)
			{
				mp_ubyte buffer[29];
				memset(buffer, 0, sizeof(buffer));
				f.read(buffer, 1, instr[y].size - 4);
				memcpy(instr[y].name, buffer, 22);
				instr[y].type = buffer[22];
				instr[y].samp = LittleEndian::GET_WORD(buffer + 23);
			}
			else
			{
				f.read(&instr[y].name,1,22);
				f.read(&instr[y].type,1,1);
				f.readWords(&instr[y].samp,1);
			}
			if (instr[y].samp > MP_MAXINSSAMPS)
				return MP_LOADER_FAILED;

			//printf("%i, %i\n", instr[y].size, instr[y].samp);

			if (instr[y].size <= 29)
			{
#ifdef MILKYTRACKER
				s+=16;
#endif
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
				continue;
			}

			f.readDwords(&instr[y].shsize,1);
#ifdef VERBOSE
			printf("%i/%i: %i, %i, %i, %s\n",y,header->insnum-1,instr[y].size,instr[y].shsize,instr[y].samp,instr[y].name);
#endif
			memset(insData, 0, 230);

			if (instr[y].size - 33 > 230)
			{
				//return -7;
				break;
			}

			f.read(insData, 1, instr[y].size - 33);

			/*printf("%i\r\n",instr[y].size);
			printf("%s\r\n",instr[y].name);
			printf("%i\r\n",instr[y].type);
			printf("%i\r\n",instr[y].samp);
			printf("%i\r\n",instr[y].shsize);*/
			//getch();

			memset(smpReloc, 0, sizeof(smpReloc));

			if (instr[y].samp) {
				mp_ubyte* insDataPtr = insData;

				//f.read(&nbu,1,96);

				memcpy(nbu, insDataPtr, MP_MAXINSSAMPS);
				insDataPtr+=MP_MAXINSSAMPS;

				TEnvelope venv;
				TEnvelope penv;
				memset(&venv,0,sizeof(venv));
				memset(&penv,0,sizeof(penv));

				mp_sint32 k;
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					venv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					venv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}
				for (k = 0; k < XM_ENVELOPENUMPOINTS; k++)
				{
					penv.env[k][0] = LittleEndian::GET_WORD(insDataPtr);
					penv.env[k][1] = LittleEndian::GET_WORD(insDataPtr+2);
					insDataPtr+=4;
				}

				venv.num = *insDataPtr++;
				if (venv.num > XM_ENVELOPENUMPOINTS) venv.num = XM_ENVELOPENUMPOINTS;
				penv.num = *insDataPtr++;
				if (penv.num > XM_ENVELOPENUMPOINTS) penv.num = XM_ENVELOPENUMPOINTS;
				venv.sustain = *insDataPtr++;
				venv.loops = *insDataPtr++;
				venv.loope = *insDataPtr++;
				penv.sustain = *insDataPtr++;
				penv.loops = *insDataPtr++;
				penv.loope = *insDataPtr++;
				venv.type = *insDataPtr++;
				penv.type = *insDataPtr++;

				mp_ubyte vibtype, vibsweep, vibdepth, vibrate;
				mp_uword volfade;

				vibtype = *insDataPtr++;
				vibsweep = *insDataPtr++;
				vibdepth = *insDataPtr++;
				vibrate = *insDataPtr++;

				vibdepth<<=1;

				//f.readWords(&volfade,1);
				volfade = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;
				volfade<<=1;

				//instr[y].res = LittleEndian::GET_WORD(insDataPtr);
				insDataPtr+=2;

				for (mp_sint32 l=0;l<XM_ENVELOPENUMPOINTS;l++) {
					venv.env[l][1]<<=2;
					penv.env[l][1]<<=2;
				}

				if (!module->addVolumeEnvelope(venv))
					return MP_OUT_OF_MEMORY;
				if (!module->addPanningEnvelope(penv))
					return MP_OUT_OF_MEMORY;

				mp_sint32 g=0, sc;
				for (sc=0;sc<instr[y].samp;sc++) {
					//TXMSample* smpl = &smp[g+s];

					smp[g+s].flags=3;
					smp[g+s].venvnum=e+1;
					smp[g+s].penvnum=e+1;

					smp[g+s].vibtype=vibtype;
					smp[g+s].vibsweep=vibsweep;
					smp[g+s].vibdepth=vibdepth;
					smp[g+s].vibrate=vibrate;
					smp[g+s].volfade=volfade;

					// not sure why I did that, actually doesn't make sense
					//if (!(venv.type&1)) smp[g+s].volfade=0;

					f.readDwords(&smp[g+s].samplen,1);

					f.readDwords(&smp[g+s].loopstart,1);
					f.readDwords(&smp[g+s].looplen,1);
					smp[g+s].vol=XModule::vol64to255(f.readByte());
					//f.read(&smp[g+s].vol,1,1);
					f.read(&smp[g+s].finetune,1,1);
					f.read(&smp[g+s].type,1,1);
#ifdef VERBOSE
					printf("Before: %i, After: %i\n", smp[g+s].type, smp[g+s].type & (3+16));
#endif
					f.read(&smp[g+s].pan,1,1);
					f.read(&smp[g+s].relnote,1,1);
					f.read(&smp[g+s].res,1,1);
					f.read(&smp[g+s].name,1,22);

					char line[30];
					memset(line, 0, sizeof(line));
					XModule::convertStr(line, smp[g+s].name, 23, false);
					if (line[0])
						module->addSongMessageLine(line);

#ifndef MILKYTRACKER
					// ignore empty samples when not being a tracker
					if (smp[g+s].samplen) {
						smpReloc[sc] = g;
						g++;
					}
					else
						smpReloc[sc] = -1;
#else
					smpReloc[sc] = g;
					g++;
#endif
				}

				instr[y].samp = g;

				for (sc = 0; sc < MP_MAXINSSAMPS; sc++) {
					if (smpReloc[nbu[sc]] == -1)
						instr[y].snum[sc] = -1;
					else
						instr[y].snum[sc] = smpReloc[nbu[sc]]+s;
				}

				for (sc=0;sc<instr[y].samp;sc++) {

					if (smp[s].samplen)
					{
						bool adpcm = (smp[s].res == 0xAD);

						mp_uint32 oldSize = smp[s].samplen;
						if (smp[s].type&16)
						{
							smp[s].samplen>>=1;
							smp[s].loopstart>>=1;
							smp[s].looplen>>=1;
						}

						mp_sint32 result = module->loadModuleSample(f, s,
													 adpcm ? XModule::ST_PACKING_ADPCM : XModule::ST_DELTA,
													 adpcm ? (XModule::ST_PACKING_ADPCM | XModule::ST_16BIT) : (XModule::ST_DELTA | XModule::ST_16BIT),
													 oldSize);
						if (result != MP_OK)
							return result;

						if (adpcm)
							smp[s].res = 0;
					}

					s++;

					if (s>=MP_MAXSAMPLES)
						return MP_OUT_OF_MEMORY;

				}

				e++;

			}
			else
			{
				for (mp_sint32 i = 0; i < 120; i++)
					instr[y].snum[i] = -1;
			}

#ifdef MILKYTRACKER
			s+=16 - instr[y].samp;
#endif

		}

		header->smpnum=s;
		header->volenvnum=e;
		header->panenvnum=e;

	}
	else
	{
		mp_sint32 s = 0;
		for (y=0;y<header->insnum;y++) {
			for (sc=0;sc<instr[y].samp;sc++) {

				if (smp[s].samplen)
				{
					mp_uint32 oldSize = smp[s].samplen;
					if (smp[s].type&16)
					{
						smp[s].samplen>>=1;
						smp[s].loopstart>>=1;
						smp[s].looplen>>=1;
					}

					mp_sint32 result = module->loadModuleSample(f, s, XModule::ST_DELTA, XModule::ST_DELTA | XModule::ST_16BIT, oldSize);
					if (result != MP_OK)
						return result;
				}

				s++;

				if (s>=MP_MAXSAMPLES)
					return MP_OUT_OF_MEMORY;
			}

#ifdef MILKYTRACKER
			s+=16 - instr[y].samp;
#endif

		}
	}

	// convert modplug stereo samples
	for (mp_sint32 s = 0; s < header->smpnum; s++)
	{
		if (smp[s].type & 32)
		{
			// that's what's allowed, stupid modplug tracker
			smp[s].type &= 3+16;

			if (smp[s].sample == NULL)
				continue;

			if (!(smp[s].type&16)) {
				smp[s].samplen>>=1;
				smp[s].loopstart>>=1;
				smp[s].looplen>>=1;

				mp_sbyte* sample = (mp_sbyte*)smp[s].sample;
				mp_sint32 samplen = smp[s].samplen;
				for (mp_sint32 i = 0; i < samplen; i++)
				{
					mp_sint32 s = ((mp_sint32)sample[i] + (mp_sint32)sample[i + samplen]) >> 1;
					if (s < -128) s = -128;
					if (s > 127) s = 127;
					sample[i] = (mp_sbyte)s;
				}
			}
			else
			{
				smp[s].samplen>>=1;
				smp[s].loopstart>>=1;
				smp[s].looplen>>=1;

				mp_sword* sample = (mp_sword*)smp[s].sample;
				mp_sint32 samplen = smp[s].samplen;
				for (mp_sint32 i = 0; i < samplen; i++)
				{
					mp_sint32 s = ((mp_sint32)sample[i] + (mp_sint32)sample[i + samplen]) >> 1;
					if (s < -32768) s = -32768;
					if (s > 32767) s = 32767;
					sample[i] = (mp_sword)s;
				}
			}
		}

		// correct loop type 0x03 (undefined)
		// will become ping pong loop
		// note that FT2 will refuse to load XM files with such a loop type
		if ((smp[s].type & 0x3) == 0x3)
			smp[s].type&=~1;
	}

	// correct number of patterns if necessary, otherwise the post processing will remove
	// the "invalid" patterns from the order list
	bool addPatterns = false;
	for (i = 0; i < header->ordnum; i++)
		if (header->ord[i]+1 > header->patnum)
		{
			header->patnum = header->ord[i]+1;
			addPatterns = true;
		}

	// if the pattern number has been adjusted, add some empty patterns
	if (addPatterns)
	{
		for (i = 0; i < header->patnum; i++)
			if (phead[i].patternData == NULL)
			{
				phead[i].rows = 64;
				phead[i].effnum = 2;
				phead[i].channum = (mp_ubyte)header->channum;

				phead[i].patternData = new mp_ubyte[phead[i].rows*header->channum*6];

				// out of memory?
				if (phead[i].patternData == NULL)
				{
					return MP_OUT_OF_MEMORY;
				}

				memset(phead[i].patternData,0,phead[i].rows*header->channum*6);
			}
	}

	// check for MODPLUG extensions
	if (f.posWithBaseOffset() + 8 <= fileSize)
	{
		char buffer[4];
		f.read(buffer, 1, 4);
		if (memcmp(buffer, "text", 4) == 0)
		{
			mp_uint32 len = f.readDword();
			module->allocateSongMessage(len+1);

			memset(module->message, 0, len+1);

			f.read(module->message, 1, len);
		}
	}

	module->postProcessSamples();

	return MP_OK;
}


ex_substitute(exarg_T *eap)
{
    linenr_T	lnum;
    long	i = 0;
    regmmatch_T regmatch;
    static subflags_T subflags = {FALSE, FALSE, FALSE, TRUE, FALSE,
							      FALSE, FALSE, 0};
#ifdef FEAT_EVAL
    subflags_T	subflags_save;
#endif
    int		save_do_all;		// remember user specified 'g' flag
    int		save_do_ask;		// remember user specified 'c' flag
    char_u	*pat = NULL, *sub = NULL;	// init for GCC
    int		delimiter;
    int		sublen;
    int		got_quit = FALSE;
    int		got_match = FALSE;
    int		temp;
    int		which_pat;
    char_u	*cmd;
    int		save_State;
    linenr_T	first_line = 0;		// first changed line
    linenr_T	last_line= 0;		// below last changed line AFTER the
					// change
    linenr_T	old_line_count = curbuf->b_ml.ml_line_count;
    linenr_T	line2;
    long	nmatch;			// number of lines in match
    char_u	*sub_firstline;		// allocated copy of first sub line
    int		endcolumn = FALSE;	// cursor in last column when done
    pos_T	old_cursor = curwin->w_cursor;
    int		start_nsubs;
#ifdef FEAT_EVAL
    int		save_ma = 0;
#endif

    cmd = eap->arg;
    if (!global_busy)
    {
	sub_nsubs = 0;
	sub_nlines = 0;
    }
    start_nsubs = sub_nsubs;

    if (eap->cmdidx == CMD_tilde)
	which_pat = RE_LAST;	// use last used regexp
    else
	which_pat = RE_SUBST;	// use last substitute regexp

				// new pattern and substitution
    if (eap->cmd[0] == 's' && *cmd != NUL && !VIM_ISWHITE(*cmd)
		&& vim_strchr((char_u *)"0123456789cegriIp|\"", *cmd) == NULL)
    {
				// don't accept alphanumeric for separator
	if (check_regexp_delim(*cmd) == FAIL)
	    return;
#ifdef FEAT_EVAL
	if (in_vim9script() && check_global_and_subst(eap->cmd, eap->arg)
								      == FAIL)
	    return;
#endif

	/*
	 * undocumented vi feature:
	 *  "\/sub/" and "\?sub?" use last used search pattern (almost like
	 *  //sub/r).  "\&sub&" use last substitute pattern (like //sub/).
	 */
	if (*cmd == '\\')
	{
	    ++cmd;
	    if (vim_strchr((char_u *)"/?&", *cmd) == NULL)
	    {
		emsg(_(e_backslash_should_be_followed_by));
		return;
	    }
	    if (*cmd != '&')
		which_pat = RE_SEARCH;	    // use last '/' pattern
	    pat = (char_u *)"";		    // empty search pattern
	    delimiter = *cmd++;		    // remember delimiter character
	}
	else		// find the end of the regexp
	{
	    which_pat = RE_LAST;	    // use last used regexp
	    delimiter = *cmd++;		    // remember delimiter character
	    pat = cmd;			    // remember start of search pat
	    cmd = skip_regexp_ex(cmd, delimiter, magic_isset(),
							&eap->arg, NULL, NULL);
	    if (cmd[0] == delimiter)	    // end delimiter found
		*cmd++ = NUL;		    // replace it with a NUL
	}

	/*
	 * Small incompatibility: vi sees '\n' as end of the command, but in
	 * Vim we want to use '\n' to find/substitute a NUL.
	 */
	sub = cmd;	    // remember the start of the substitution
	cmd = skip_substitute(cmd, delimiter);

	if (!eap->skip)
	{
	    // In POSIX vi ":s/pat/%/" uses the previous subst. string.
	    if (STRCMP(sub, "%") == 0
				 && vim_strchr(p_cpo, CPO_SUBPERCENT) != NULL)
	    {
		if (old_sub == NULL)	// there is no previous command
		{
		    emsg(_(e_no_previous_substitute_regular_expression));
		    return;
		}
		sub = old_sub;
	    }
	    else
	    {
		vim_free(old_sub);
		old_sub = vim_strsave(sub);
	    }
	}
    }
    else if (!eap->skip)	// use previous pattern and substitution
    {
	if (old_sub == NULL)	// there is no previous command
	{
	    emsg(_(e_no_previous_substitute_regular_expression));
	    return;
	}
	pat = NULL;		// search_regcomp() will use previous pattern
	sub = old_sub;

	// Vi compatibility quirk: repeating with ":s" keeps the cursor in the
	// last column after using "$".
	endcolumn = (curwin->w_curswant == MAXCOL);
    }

    // Recognize ":%s/\n//" and turn it into a join command, which is much
    // more efficient.
    // TODO: find a generic solution to make line-joining operations more
    // efficient, avoid allocating a string that grows in size.
    if (pat != NULL && STRCMP(pat, "\\n") == 0
	    && *sub == NUL
	    && (*cmd == NUL || (cmd[1] == NUL && (*cmd == 'g' || *cmd == 'l'
					     || *cmd == 'p' || *cmd == '#'))))
    {
	linenr_T    joined_lines_count;

	if (eap->skip)
	    return;
	curwin->w_cursor.lnum = eap->line1;
	if (*cmd == 'l')
	    eap->flags = EXFLAG_LIST;
	else if (*cmd == '#')
	    eap->flags = EXFLAG_NR;
	else if (*cmd == 'p')
	    eap->flags = EXFLAG_PRINT;

	// The number of lines joined is the number of lines in the range plus
	// one.  One less when the last line is included.
	joined_lines_count = eap->line2 - eap->line1 + 1;
	if (eap->line2 < curbuf->b_ml.ml_line_count)
	    ++joined_lines_count;
	if (joined_lines_count > 1)
	{
	    (void)do_join(joined_lines_count, FALSE, TRUE, FALSE, TRUE);
	    sub_nsubs = joined_lines_count - 1;
	    sub_nlines = 1;
	    (void)do_sub_msg(FALSE);
	    ex_may_print(eap);
	}

	if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0)
	    save_re_pat(RE_SUBST, pat, magic_isset());
	// put pattern in history
	add_to_history(HIST_SEARCH, pat, TRUE, NUL);

	return;
    }

    /*
     * Find trailing options.  When '&' is used, keep old options.
     */
    if (*cmd == '&')
	++cmd;
    else
    {
#ifdef FEAT_EVAL
	if (in_vim9script())
	{
	    // ignore 'gdefault' and 'edcompatible'
	    subflags.do_all = FALSE;
	    subflags.do_ask = FALSE;
	}
	else
#endif
	if (!p_ed)
	{
	    if (p_gd)		// default is global on
		subflags.do_all = TRUE;
	    else
		subflags.do_all = FALSE;
	    subflags.do_ask = FALSE;
	}
	subflags.do_error = TRUE;
	subflags.do_print = FALSE;
	subflags.do_list = FALSE;
	subflags.do_count = FALSE;
	subflags.do_number = FALSE;
	subflags.do_ic = 0;
    }
    while (*cmd)
    {
	/*
	 * Note that 'g' and 'c' are always inverted, also when p_ed is off.
	 * 'r' is never inverted.
	 */
	if (*cmd == 'g')
	    subflags.do_all = !subflags.do_all;
	else if (*cmd == 'c')
	    subflags.do_ask = !subflags.do_ask;
	else if (*cmd == 'n')
	    subflags.do_count = TRUE;
	else if (*cmd == 'e')
	    subflags.do_error = !subflags.do_error;
	else if (*cmd == 'r')	    // use last used regexp
	    which_pat = RE_LAST;
	else if (*cmd == 'p')
	    subflags.do_print = TRUE;
	else if (*cmd == '#')
	{
	    subflags.do_print = TRUE;
	    subflags.do_number = TRUE;
	}
	else if (*cmd == 'l')
	{
	    subflags.do_print = TRUE;
	    subflags.do_list = TRUE;
	}
	else if (*cmd == 'i')	    // ignore case
	    subflags.do_ic = 'i';
	else if (*cmd == 'I')	    // don't ignore case
	    subflags.do_ic = 'I';
	else
	    break;
	++cmd;
    }
    if (subflags.do_count)
	subflags.do_ask = FALSE;

    save_do_all = subflags.do_all;
    save_do_ask = subflags.do_ask;

    /*
     * check for a trailing count
     */
    cmd = skipwhite(cmd);
    if (VIM_ISDIGIT(*cmd))
    {
	i = getdigits(&cmd);
	if (i <= 0 && !eap->skip && subflags.do_error)
	{
	    emsg(_(e_positive_count_required));
	    return;
	}
	eap->line1 = eap->line2;
	eap->line2 += i - 1;
	if (eap->line2 > curbuf->b_ml.ml_line_count)
	    eap->line2 = curbuf->b_ml.ml_line_count;
    }

    /*
     * check for trailing command or garbage
     */
    cmd = skipwhite(cmd);
    if (*cmd && *cmd != '"')	    // if not end-of-line or comment
    {
	set_nextcmd(eap, cmd);
	if (eap->nextcmd == NULL)
	{
	    semsg(_(e_trailing_characters_str), cmd);
	    return;
	}
    }

    if (eap->skip)	    // not executing commands, only parsing
	return;

    if (!subflags.do_count && !curbuf->b_p_ma)
    {
	// Substitution is not allowed in non-'modifiable' buffer
	emsg(_(e_cannot_make_changes_modifiable_is_off));
	return;
    }

    if (search_regcomp(pat, RE_SUBST, which_pat, SEARCH_HIS, &regmatch) == FAIL)
    {
	if (subflags.do_error)
	    emsg(_(e_invalid_command));
	return;
    }

    // the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase'
    if (subflags.do_ic == 'i')
	regmatch.rmm_ic = TRUE;
    else if (subflags.do_ic == 'I')
	regmatch.rmm_ic = FALSE;

    sub_firstline = NULL;

    /*
     * ~ in the substitute pattern is replaced with the old pattern.
     * We do it here once to avoid it to be replaced over and over again.
     * But don't do it when it starts with "\=", then it's an expression.
     */
    if (!(sub[0] == '\\' && sub[1] == '='))
	sub = regtilde(sub, magic_isset());

    /*
     * Check for a match on each line.
     */
    line2 = eap->line2;
    for (lnum = eap->line1; lnum <= line2 && !(got_quit
#if defined(FEAT_EVAL)
		|| aborting()
#endif
		); ++lnum)
    {
	nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
						       (colnr_T)0, NULL, NULL);
	if (nmatch)
	{
	    colnr_T	copycol;
	    colnr_T	matchcol;
	    colnr_T	prev_matchcol = MAXCOL;
	    char_u	*new_end, *new_start = NULL;
	    unsigned	new_start_len = 0;
	    char_u	*p1;
	    int		did_sub = FALSE;
	    int		lastone;
	    int		len, copy_len, needed_len;
	    long	nmatch_tl = 0;	// nr of lines matched below lnum
	    int		do_again;	// do it again after joining lines
	    int		skip_match = FALSE;
	    linenr_T	sub_firstlnum;	// nr of first sub line
#ifdef FEAT_PROP_POPUP
	    int		apc_flags = APC_SAVE_FOR_UNDO | APC_SUBSTITUTE;
	    colnr_T	total_added =  0;
#endif

	    /*
	     * The new text is build up step by step, to avoid too much
	     * copying.  There are these pieces:
	     * sub_firstline	The old text, unmodified.
	     * copycol		Column in the old text where we started
	     *			looking for a match; from here old text still
	     *			needs to be copied to the new text.
	     * matchcol		Column number of the old text where to look
	     *			for the next match.  It's just after the
	     *			previous match or one further.
	     * prev_matchcol	Column just after the previous match (if any).
	     *			Mostly equal to matchcol, except for the first
	     *			match and after skipping an empty match.
	     * regmatch.*pos	Where the pattern matched in the old text.
	     * new_start	The new text, all that has been produced so
	     *			far.
	     * new_end		The new text, where to append new text.
	     *
	     * lnum		The line number where we found the start of
	     *			the match.  Can be below the line we searched
	     *			when there is a \n before a \zs in the
	     *			pattern.
	     * sub_firstlnum	The line number in the buffer where to look
	     *			for a match.  Can be different from "lnum"
	     *			when the pattern or substitute string contains
	     *			line breaks.
	     *
	     * Special situations:
	     * - When the substitute string contains a line break, the part up
	     *   to the line break is inserted in the text, but the copy of
	     *   the original line is kept.  "sub_firstlnum" is adjusted for
	     *   the inserted lines.
	     * - When the matched pattern contains a line break, the old line
	     *   is taken from the line at the end of the pattern.  The lines
	     *   in the match are deleted later, "sub_firstlnum" is adjusted
	     *   accordingly.
	     *
	     * The new text is built up in new_start[].  It has some extra
	     * room to avoid using alloc()/free() too often.  new_start_len is
	     * the length of the allocated memory at new_start.
	     *
	     * Make a copy of the old line, so it won't be taken away when
	     * updating the screen or handling a multi-line match.  The "old_"
	     * pointers point into this copy.
	     */
	    sub_firstlnum = lnum;
	    copycol = 0;
	    matchcol = 0;

	    // At first match, remember current cursor position.
	    if (!got_match)
	    {
		setpcmark();
		got_match = TRUE;
	    }

	    /*
	     * Loop until nothing more to replace in this line.
	     * 1. Handle match with empty string.
	     * 2. If do_ask is set, ask for confirmation.
	     * 3. substitute the string.
	     * 4. if do_all is set, find next match
	     * 5. break if there isn't another match in this line
	     */
	    for (;;)
	    {
		// Advance "lnum" to the line where the match starts.  The
		// match does not start in the first line when there is a line
		// break before \zs.
		if (regmatch.startpos[0].lnum > 0)
		{
		    lnum += regmatch.startpos[0].lnum;
		    sub_firstlnum += regmatch.startpos[0].lnum;
		    nmatch -= regmatch.startpos[0].lnum;
		    VIM_CLEAR(sub_firstline);
		}

		// Match might be after the last line for "\n\zs" matching at
		// the end of the last line.
		if (lnum > curbuf->b_ml.ml_line_count)
		    break;

		if (sub_firstline == NULL)
		{
		    sub_firstline = vim_strsave(ml_get(sub_firstlnum));
		    if (sub_firstline == NULL)
		    {
			vim_free(new_start);
			goto outofmem;
		    }
		}

		// Save the line number of the last change for the final
		// cursor position (just like Vi).
		curwin->w_cursor.lnum = lnum;
		do_again = FALSE;

		/*
		 * 1. Match empty string does not count, except for first
		 * match.  This reproduces the strange vi behaviour.
		 * This also catches endless loops.
		 */
		if (matchcol == prev_matchcol
			&& regmatch.endpos[0].lnum == 0
			&& matchcol == regmatch.endpos[0].col)
		{
		    if (sub_firstline[matchcol] == NUL)
			// We already were at the end of the line.  Don't look
			// for a match in this line again.
			skip_match = TRUE;
		    else
		    {
			 // search for a match at next column
			if (has_mbyte)
			    matchcol += mb_ptr2len(sub_firstline + matchcol);
			else
			    ++matchcol;
		    }
		    goto skip;
		}

		// Normally we continue searching for a match just after the
		// previous match.
		matchcol = regmatch.endpos[0].col;
		prev_matchcol = matchcol;

		/*
		 * 2. If do_count is set only increase the counter.
		 *    If do_ask is set, ask for confirmation.
		 */
		if (subflags.do_count)
		{
		    // For a multi-line match, put matchcol at the NUL at
		    // the end of the line and set nmatch to one, so that
		    // we continue looking for a match on the next line.
		    // Avoids that ":s/\nB\@=//gc" get stuck.
		    if (nmatch > 1)
		    {
			matchcol = (colnr_T)STRLEN(sub_firstline);
			nmatch = 1;
			skip_match = TRUE;
		    }
		    sub_nsubs++;
		    did_sub = TRUE;
#ifdef FEAT_EVAL
		    // Skip the substitution, unless an expression is used,
		    // then it is evaluated in the sandbox.
		    if (!(sub[0] == '\\' && sub[1] == '='))
#endif
			goto skip;
		}

		if (subflags.do_ask)
		{
		    int typed = 0;

		    // change State to CONFIRM, so that the mouse works
		    // properly
		    save_State = State;
		    State = CONFIRM;
		    setmouse();		// disable mouse in xterm
		    curwin->w_cursor.col = regmatch.startpos[0].col;
		    if (curwin->w_p_crb)
			do_check_cursorbind();

		    // When 'cpoptions' contains "u" don't sync undo when
		    // asking for confirmation.
		    if (vim_strchr(p_cpo, CPO_UNDO) != NULL)
			++no_u_sync;

		    /*
		     * Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed.
		     */
		    while (subflags.do_ask)
		    {
			if (exmode_active)
			{
			    char_u	*resp;
			    colnr_T	sc, ec;

			    print_line_no_prefix(lnum,
					 subflags.do_number, subflags.do_list);

			    getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL);
			    curwin->w_cursor.col = regmatch.endpos[0].col - 1;
			    if (curwin->w_cursor.col < 0)
				curwin->w_cursor.col = 0;
			    getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec);
			    curwin->w_cursor.col = regmatch.startpos[0].col;
			    if (subflags.do_number || curwin->w_p_nu)
			    {
				int numw = number_width(curwin) + 1;
				sc += numw;
				ec += numw;
			    }
			    msg_start();
			    for (i = 0; i < (long)sc; ++i)
				msg_putchar(' ');
			    for ( ; i <= (long)ec; ++i)
				msg_putchar('^');

			    resp = getexmodeline('?', NULL, 0, TRUE);
			    if (resp != NULL)
			    {
				typed = *resp;
				vim_free(resp);
			    }
			}
			else
			{
			    char_u *orig_line = NULL;
			    int    len_change = 0;
			    int	   save_p_lz = p_lz;
#ifdef FEAT_FOLDING
			    int save_p_fen = curwin->w_p_fen;

			    curwin->w_p_fen = FALSE;
#endif
			    // Invert the matched string.
			    // Remove the inversion afterwards.
			    temp = RedrawingDisabled;
			    RedrawingDisabled = 0;

			    // avoid calling update_screen() in vgetorpeek()
			    p_lz = FALSE;

			    if (new_start != NULL)
			    {
				// There already was a substitution, we would
				// like to show this to the user.  We cannot
				// really update the line, it would change
				// what matches.  Temporarily replace the line
				// and change it back afterwards.
				orig_line = vim_strsave(ml_get(lnum));
				if (orig_line != NULL)
				{
				    char_u *new_line = concat_str(new_start,
						     sub_firstline + copycol);

				    if (new_line == NULL)
					VIM_CLEAR(orig_line);
				    else
				    {
					// Position the cursor relative to the
					// end of the line, the previous
					// substitute may have inserted or
					// deleted characters before the
					// cursor.
					len_change = (int)STRLEN(new_line)
						     - (int)STRLEN(orig_line);
					curwin->w_cursor.col += len_change;
					ml_replace(lnum, new_line, FALSE);
				    }
				}
			    }

			    search_match_lines = regmatch.endpos[0].lnum
						  - regmatch.startpos[0].lnum;
			    search_match_endcol = regmatch.endpos[0].col
								 + len_change;
			    highlight_match = TRUE;

			    update_topline();
			    validate_cursor();
			    update_screen(SOME_VALID);
			    highlight_match = FALSE;
			    redraw_later(SOME_VALID);

#ifdef FEAT_FOLDING
			    curwin->w_p_fen = save_p_fen;
#endif
			    if (msg_row == Rows - 1)
				msg_didout = FALSE;	// avoid a scroll-up
			    msg_starthere();
			    i = msg_scroll;
			    msg_scroll = 0;		// truncate msg when
							// needed
			    msg_no_more = TRUE;
			    // write message same highlighting as for
			    // wait_return
			    smsg_attr(HL_ATTR(HLF_R),
				_("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
			    msg_no_more = FALSE;
			    msg_scroll = i;
			    showruler(TRUE);
			    windgoto(msg_row, msg_col);
			    RedrawingDisabled = temp;

#ifdef USE_ON_FLY_SCROLL
			    dont_scroll = FALSE; // allow scrolling here
#endif
			    ++no_mapping;	// don't map this key
			    ++allow_keys;	// allow special keys
			    typed = plain_vgetc();
			    --allow_keys;
			    --no_mapping;

			    // clear the question
			    msg_didout = FALSE;	// don't scroll up
			    msg_col = 0;
			    gotocmdline(TRUE);
			    p_lz = save_p_lz;

			    // restore the line
			    if (orig_line != NULL)
				ml_replace(lnum, orig_line, FALSE);
			}

			need_wait_return = FALSE; // no hit-return prompt
			if (typed == 'q' || typed == ESC || typed == Ctrl_C
#ifdef UNIX
				|| typed == intr_char
#endif
				)
			{
			    got_quit = TRUE;
			    break;
			}
			if (typed == 'n')
			    break;
			if (typed == 'y')
			    break;
			if (typed == 'l')
			{
			    // last: replace and then stop
			    subflags.do_all = FALSE;
			    line2 = lnum;
			    break;
			}
			if (typed == 'a')
			{
			    subflags.do_ask = FALSE;
			    break;
			}
			if (typed == Ctrl_E)
			    scrollup_clamp();
			else if (typed == Ctrl_Y)
			    scrolldown_clamp();
		    }
		    State = save_State;
		    setmouse();
		    if (vim_strchr(p_cpo, CPO_UNDO) != NULL)
			--no_u_sync;

		    if (typed == 'n')
		    {
			// For a multi-line match, put matchcol at the NUL at
			// the end of the line and set nmatch to one, so that
			// we continue looking for a match on the next line.
			// Avoids that ":%s/\nB\@=//gc" and ":%s/\n/,\r/gc"
			// get stuck when pressing 'n'.
			if (nmatch > 1)
			{
			    matchcol = (colnr_T)STRLEN(sub_firstline);
			    skip_match = TRUE;
			}
			goto skip;
		    }
		    if (got_quit)
			goto skip;
		}

		// Move the cursor to the start of the match, so that we can
		// use "\=col(".").
		curwin->w_cursor.col = regmatch.startpos[0].col;

		/*
		 * 3. substitute the string.
		 */
#ifdef FEAT_EVAL
		save_ma = curbuf->b_p_ma;
		if (subflags.do_count)
		{
		    // prevent accidentally changing the buffer by a function
		    curbuf->b_p_ma = FALSE;
		    sandbox++;
		}
		// Save flags for recursion.  They can change for e.g.
		// :s/^/\=execute("s#^##gn")
		subflags_save = subflags;
#endif
		// get length of substitution part
		sublen = vim_regsub_multi(&regmatch,
				    sub_firstlnum - regmatch.startpos[0].lnum,
			       sub, sub_firstline, FALSE, magic_isset(), TRUE);
#ifdef FEAT_EVAL
		// If getting the substitute string caused an error, don't do
		// the replacement.
		// Don't keep flags set by a recursive call.
		subflags = subflags_save;
		if (aborting() || subflags.do_count)
		{
		    curbuf->b_p_ma = save_ma;
		    if (sandbox > 0)
			sandbox--;
		    goto skip;
		}
#endif

		// When the match included the "$" of the last line it may
		// go beyond the last line of the buffer.
		if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1)
		{
		    nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1;
		    skip_match = TRUE;
		}

		// Need room for:
		// - result so far in new_start (not for first sub in line)
		// - original text up to match
		// - length of substituted part
		// - original text after match
		// Adjust text properties here, since we have all information
		// needed.
		if (nmatch == 1)
		{
		    p1 = sub_firstline;
#ifdef FEAT_PROP_POPUP
		    if (curbuf->b_has_textprop)
		    {
			int bytes_added = sublen - 1 - (regmatch.endpos[0].col
						   - regmatch.startpos[0].col);

			// When text properties are changed, need to save for
			// undo first, unless done already.
			if (adjust_prop_columns(lnum,
					total_added + regmatch.startpos[0].col,
						       bytes_added, apc_flags))
			    apc_flags &= ~APC_SAVE_FOR_UNDO;
			// Offset for column byte number of the text property
			// in the resulting buffer afterwards.
			total_added += bytes_added;
		    }
#endif
		}
		else
		{
		    p1 = ml_get(sub_firstlnum + nmatch - 1);
		    nmatch_tl += nmatch - 1;
		}
		copy_len = regmatch.startpos[0].col - copycol;
		needed_len = copy_len + ((unsigned)STRLEN(p1)
				       - regmatch.endpos[0].col) + sublen + 1;
		if (new_start == NULL)
		{
		    /*
		     * Get some space for a temporary buffer to do the
		     * substitution into (and some extra space to avoid
		     * too many calls to alloc()/free()).
		     */
		    new_start_len = needed_len + 50;
		    if ((new_start = alloc(new_start_len)) == NULL)
			goto outofmem;
		    *new_start = NUL;
		    new_end = new_start;
		}
		else
		{
		    /*
		     * Check if the temporary buffer is long enough to do the
		     * substitution into.  If not, make it larger (with a bit
		     * extra to avoid too many calls to alloc()/free()).
		     */
		    len = (unsigned)STRLEN(new_start);
		    needed_len += len;
		    if (needed_len > (int)new_start_len)
		    {
			new_start_len = needed_len + 50;
			if ((p1 = alloc(new_start_len)) == NULL)
			{
			    vim_free(new_start);
			    goto outofmem;
			}
			mch_memmove(p1, new_start, (size_t)(len + 1));
			vim_free(new_start);
			new_start = p1;
		    }
		    new_end = new_start + len;
		}

		/*
		 * copy the text up to the part that matched
		 */
		mch_memmove(new_end, sub_firstline + copycol, (size_t)copy_len);
		new_end += copy_len;

		(void)vim_regsub_multi(&regmatch,
				    sub_firstlnum - regmatch.startpos[0].lnum,
				      sub, new_end, TRUE, magic_isset(), TRUE);
		sub_nsubs++;
		did_sub = TRUE;

		// Move the cursor to the start of the line, to avoid that it
		// is beyond the end of the line after the substitution.
		curwin->w_cursor.col = 0;

		// For a multi-line match, make a copy of the last matched
		// line and continue in that one.
		if (nmatch > 1)
		{
		    sub_firstlnum += nmatch - 1;
		    vim_free(sub_firstline);
		    sub_firstline = vim_strsave(ml_get(sub_firstlnum));
		    // When going beyond the last line, stop substituting.
		    if (sub_firstlnum <= line2)
			do_again = TRUE;
		    else
			subflags.do_all = FALSE;
		}

		// Remember next character to be copied.
		copycol = regmatch.endpos[0].col;

		if (skip_match)
		{
		    // Already hit end of the buffer, sub_firstlnum is one
		    // less than what it ought to be.
		    vim_free(sub_firstline);
		    sub_firstline = vim_strsave((char_u *)"");
		    copycol = 0;
		}

		/*
		 * Now the trick is to replace CTRL-M chars with a real line
		 * break.  This would make it impossible to insert a CTRL-M in
		 * the text.  The line break can be avoided by preceding the
		 * CTRL-M with a backslash.  To be able to insert a backslash,
		 * they must be doubled in the string and are halved here.
		 * That is Vi compatible.
		 */
		for (p1 = new_end; *p1; ++p1)
		{
		    if (p1[0] == '\\' && p1[1] != NUL)  // remove backslash
		    {
			STRMOVE(p1, p1 + 1);
#ifdef FEAT_PROP_POPUP
			if (curbuf->b_has_textprop)
			{
			    // When text properties are changed, need to save
			    // for undo first, unless done already.
			    if (adjust_prop_columns(lnum,
					(colnr_T)(p1 - new_start), -1,
					apc_flags))
				apc_flags &= ~APC_SAVE_FOR_UNDO;
			}
#endif
		    }
		    else if (*p1 == CAR)
		    {
			if (u_inssub(lnum) == OK)   // prepare for undo
			{
			    colnr_T	plen = (colnr_T)(p1 - new_start + 1);

			    *p1 = NUL;		    // truncate up to the CR
			    ml_append(lnum - 1, new_start, plen, FALSE);
			    mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L);
			    if (subflags.do_ask)
				appended_lines(lnum - 1, 1L);
			    else
			    {
				if (first_line == 0)
				    first_line = lnum;
				last_line = lnum + 1;
			    }
#ifdef FEAT_PROP_POPUP
			    adjust_props_for_split(lnum + 1, lnum, plen, 1);
#endif
			    // all line numbers increase
			    ++sub_firstlnum;
			    ++lnum;
			    ++line2;
			    // move the cursor to the new line, like Vi
			    ++curwin->w_cursor.lnum;
			    // copy the rest
			    STRMOVE(new_start, p1 + 1);
			    p1 = new_start - 1;
			}
		    }
		    else if (has_mbyte)
			p1 += (*mb_ptr2len)(p1) - 1;
		}

		/*
		 * 4. If do_all is set, find next match.
		 * Prevent endless loop with patterns that match empty
		 * strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g.
		 * But ":s/\n/#/" is OK.
		 */
skip:
		// We already know that we did the last subst when we are at
		// the end of the line, except that a pattern like
		// "bar\|\nfoo" may match at the NUL.  "lnum" can be below
		// "line2" when there is a \zs in the pattern after a line
		// break.
		lastone = (skip_match
			|| got_int
			|| got_quit
			|| lnum > line2
			|| !(subflags.do_all || do_again)
			|| (sub_firstline[matchcol] == NUL && nmatch <= 1
					 && !re_multiline(regmatch.regprog)));
		nmatch = -1;

		/*
		 * Replace the line in the buffer when needed.  This is
		 * skipped when there are more matches.
		 * The check for nmatch_tl is needed for when multi-line
		 * matching must replace the lines before trying to do another
		 * match, otherwise "\@<=" won't work.
		 * When the match starts below where we start searching also
		 * need to replace the line first (using \zs after \n).
		 */
		if (lastone
			|| nmatch_tl > 0
			|| (nmatch = vim_regexec_multi(&regmatch, curwin,
							curbuf, sub_firstlnum,
						    matchcol, NULL, NULL)) == 0
			|| regmatch.startpos[0].lnum > 0)
		{
		    if (new_start != NULL)
		    {
			/*
			 * Copy the rest of the line, that didn't match.
			 * "matchcol" has to be adjusted, we use the end of
			 * the line as reference, because the substitute may
			 * have changed the number of characters.  Same for
			 * "prev_matchcol".
			 */
			STRCAT(new_start, sub_firstline + copycol);
			matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol;
			prev_matchcol = (colnr_T)STRLEN(sub_firstline)
							      - prev_matchcol;

			if (u_savesub(lnum) != OK)
			    break;
			ml_replace(lnum, new_start, TRUE);

			if (nmatch_tl > 0)
			{
			    /*
			     * Matched lines have now been substituted and are
			     * useless, delete them.  The part after the match
			     * has been appended to new_start, we don't need
			     * it in the buffer.
			     */
			    ++lnum;
			    if (u_savedel(lnum, nmatch_tl) != OK)
				break;
			    for (i = 0; i < nmatch_tl; ++i)
				ml_delete(lnum);
			    mark_adjust(lnum, lnum + nmatch_tl - 1,
						   (long)MAXLNUM, -nmatch_tl);
			    if (subflags.do_ask)
				deleted_lines(lnum, nmatch_tl);
			    --lnum;
			    line2 -= nmatch_tl; // nr of lines decreases
			    nmatch_tl = 0;
			}

			// When asking, undo is saved each time, must also set
			// changed flag each time.
			if (subflags.do_ask)
			    changed_bytes(lnum, 0);
			else
			{
			    if (first_line == 0)
				first_line = lnum;
			    last_line = lnum + 1;
			}

			sub_firstlnum = lnum;
			vim_free(sub_firstline);    // free the temp buffer
			sub_firstline = new_start;
			new_start = NULL;
			matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol;
			prev_matchcol = (colnr_T)STRLEN(sub_firstline)
							      - prev_matchcol;
			copycol = 0;
		    }
		    if (nmatch == -1 && !lastone)
			nmatch = vim_regexec_multi(&regmatch, curwin, curbuf,
					  sub_firstlnum, matchcol, NULL, NULL);

		    /*
		     * 5. break if there isn't another match in this line
		     */
		    if (nmatch <= 0)
		    {
			// If the match found didn't start where we were
			// searching, do the next search in the line where we
			// found the match.
			if (nmatch == -1)
			    lnum -= regmatch.startpos[0].lnum;
			break;
		    }
		}

		line_breakcheck();
	    }

	    if (did_sub)
		++sub_nlines;
	    vim_free(new_start);	// for when substitute was cancelled
	    VIM_CLEAR(sub_firstline);	// free the copy of the original line
	}

	line_breakcheck();
    }

    if (first_line != 0)
    {
	// Need to subtract the number of added lines from "last_line" to get
	// the line number before the change (same as adding the number of
	// deleted lines).
	i = curbuf->b_ml.ml_line_count - old_line_count;
	changed_lines(first_line, 0, last_line - i, i);
    }

outofmem:
    vim_free(sub_firstline); // may have to free allocated copy of the line

    // ":s/pat//n" doesn't move the cursor
    if (subflags.do_count)
	curwin->w_cursor = old_cursor;

    if (sub_nsubs > start_nsubs)
    {
	if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0)
	{
	    // Set the '[ and '] marks.
	    curbuf->b_op_start.lnum = eap->line1;
	    curbuf->b_op_end.lnum = line2;
	    curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
	}

	if (!global_busy)
	{
	    // when interactive leave cursor on the match
	    if (!subflags.do_ask)
	    {
		if (endcolumn)
		    coladvance((colnr_T)MAXCOL);
		else
		    beginline(BL_WHITE | BL_FIX);
	    }
	    if (!do_sub_msg(subflags.do_count) && subflags.do_ask)
		msg("");
	}
	else
	    global_need_beginline = TRUE;
	if (subflags.do_print)
	    print_line(curwin->w_cursor.lnum,
					 subflags.do_number, subflags.do_list);
    }
    else if (!global_busy)
    {
	if (got_int)		// interrupted
	    emsg(_(e_interrupted));
	else if (got_match)	// did find something but nothing substituted
	    msg("");
	else if (subflags.do_error)	// nothing found
	    semsg(_(e_pattern_not_found_str), get_search_pat());
    }

#ifdef FEAT_FOLDING
    if (subflags.do_ask && hasAnyFolding(curwin))
	// Cursor position may require updating
	changed_window_setting();
#endif

    vim_regfree(regmatch.regprog);

    // Restore the flag values, they can be used for ":&&".
    subflags.do_all = save_do_all;
    subflags.do_ask = save_do_ask;
}

patch 8.2.4253: using freed memory when substitute with function call

Problem:    Using freed memory when substitute uses a recursive function call.
Solution:   Make a copy of the substitute text.