#include "window.h"
#include "vwf.h"
#include "number-selector.h"
#include "locs.h"
#include "custom_codes.h"

byte decode_character(byte chr)
{
    int c = chr - CHAR_OFFSET;
    if ((c < 0) || ((c >= CHAR_END) && (c < YOUWON_START)) || (c > ARROW))
        c = QUESTION_MARK;

    return c;
}

byte encode_ascii(char chr)
{
    return (byte)(chr + 48);
}

int get_tile_number(int x, int y)
{
    x--;
    y--;
    return m2_coord_table[x + ((y >> 1) * 28)] + (y & 1) * 32;
}

int get_tile_number_buffer(int x, int y)
{
    x--;
    y--;

    //Covers the alphabet-printing issues
    if(y >= 0x10)
        y -= 0xC;
    int totalLenght = x + ((y >> 1) * 28);
    int addedValue = (totalLenght >> 5) << 6;
    return (totalLenght & 0x1F) + addedValue + (y & 1) * 32;
}

int get_tile_number_with_offset(int x, int y)
{
    return get_tile_number(x, y) + *tile_offset;
}

int get_tile_number_with_offset_buffer(int x, int y)
{
    return get_tile_number_buffer(x, y) + *tile_offset;
}

int get_tile_number_grid(int x, int y)
{
    return x + (y * 32);
}

int expand_bit_depth(byte row, byte foreground)
{
    foreground &= 0xF;
    return m2_bits_to_nybbles[row + (foreground * 256)];
}

//The foregroundRow is given as a parameter in order to make this faster
byte reduce_bit_depth(int row, int foregroundRow)
{
    row ^= foregroundRow;

    int lower = m2_nybbles_to_bits[row & 0xFFFF];
    int upper = m2_nybbles_to_bits[(row >> 16) & 0xFFFF];

    return lower | (upper << 4);
}

//The order is swapped in order to make this faster
//Doing the bottom tile directly saves some cycles
void reduce_bit_depth_sp(int* TileRows, int* bufferValues, int* bottomBufferValues)
{
    int* bottomTileRows = TileRows + (0x20 * 8);
    const int andValue = 0x11111111;
    
    //First value
    unsigned int firstRow = *(TileRows++);
    unsigned int secondRow = *(TileRows++);
    unsigned int thirdRow = *(TileRows++);
    unsigned int fourthRow = *(TileRows++);

    firstRow &= andValue;
    secondRow &= andValue;
    thirdRow &= andValue;
    fourthRow &= andValue;

    unsigned int value = optimized_byte_4bpp_to_1bpp_table[(fourthRow >> 0xF) + (fourthRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(thirdRow >> 0xF) + (thirdRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(secondRow >> 0xF) + (secondRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(firstRow >> 0xF) + (firstRow & 0xFFFF)];
    *(bufferValues++) = value;
    
    //Second value
    firstRow = *(TileRows++);
    secondRow = *(TileRows++);
    thirdRow = *(TileRows++);
    fourthRow = *(TileRows);
    
    firstRow &= andValue;
    secondRow &= andValue;
    thirdRow &= andValue;
    fourthRow &= andValue;

    value = optimized_byte_4bpp_to_1bpp_table[(fourthRow >> 0xF) + (fourthRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(thirdRow >> 0xF) + (thirdRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(secondRow >> 0xF) + (secondRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(firstRow >> 0xF) + (firstRow & 0xFFFF)];
    *(bufferValues) = value;
    
    //First value of bottom tile
    firstRow = *(bottomTileRows++);
    secondRow = *(bottomTileRows++);
    thirdRow = *(bottomTileRows++);
    fourthRow = *(bottomTileRows++);

    firstRow &= andValue;
    secondRow &= andValue;
    thirdRow &= andValue;
    fourthRow &= andValue;

    value = optimized_byte_4bpp_to_1bpp_table[(fourthRow >> 0xF) + (fourthRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(thirdRow >> 0xF) + (thirdRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(secondRow >> 0xF) + (secondRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(firstRow >> 0xF) + (firstRow & 0xFFFF)];
    *(bottomBufferValues++) = value;
    
    //Second value of bottom tile - Is not used by the game
    /*
    firstRow = *(bottomTileRows++);
    secondRow = *(bottomTileRows++);
    thirdRow = *(bottomTileRows++);
    fourthRow = *(bottomTileRows);

    firstRow &= andValue;
    secondRow &= andValue;
    thirdRow &= andValue;
    fourthRow &= andValue;

    value = optimized_byte_4bpp_to_1bpp_table[(fourthRow >> 0xF) + (fourthRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(thirdRow >> 0xF) + (thirdRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(secondRow >> 0xF) + (secondRow & 0xFFFF)];
    value <<= 8;
    value |= optimized_byte_4bpp_to_1bpp_table[(firstRow >> 0xF) + (firstRow & 0xFFFF)];
    *(bottomBufferValues) = value;
    */
}

byte getSex(byte character)
{
    return character == 1 ? 1 : 0; //character 1 is Paula
}

void getPossessive(byte character, byte *str, int *index)
{
    char his[] = "his";
    char her[] = "her";
    if(getSex(character) == 1)
        for (int i = 0; i < (sizeof(her) - 1); i++)
            str[(*index)++] = encode_ascii(her[i]);
    else
        for (int i = 0; i < (sizeof(his) - 1); i++)
            str[(*index)++] = encode_ascii(his[i]);
}

void getPronoun(byte character, byte *str, int *index)
{
    char he[] = "he";
    char she[] = "she";
    if(getSex(character) == 1)
        for (int i = 0; i < (sizeof(she) - 1); i++)
            str[(*index)++] = encode_ascii(she[i]);
    else
        for (int i = 0; i < (sizeof(he) - 1); i++)
            str[(*index)++] = encode_ascii(he[i]);
}

void getCharName(byte character, byte *str, int *index)
{
    copy_name(str, m2_ness_name, index, character * 7);
}

void copy_name(byte *str, byte *source, int *index, int pos)
{
    while(source[pos + 1] != 0xFF)
        str[(*index)++] = source[pos++];
}

int ascii_strlen(char *str)
{
    int len = 0;
    while (str[len] != 0)
        len++;
    return len;
}

int wrapper_count_pixels_to_tiles(byte *str, int length)
{
    return count_pixels_to_tiles(str, length, 0);
}

int count_pixels_to_tiles(byte *str, int length, int startingPos)
{
    int pixels = startingPos;
    for(int i = 0; i < length; i++)
    {
        if((str[i] != 0xFF) && (str[i] != 0xFE)) //The latter one is not really needed
            pixels += (m2_widths_table[0][decode_character(str[i])] & 0xFF);
        else if(str[i] == 0xFE)
        {
            // Define 0xFE as a control code
            byte cmd = str[++i];
            switch (cmd)
            {
                case CUSTOMCC_ADD_X:
                    pixels += str[++i];
                    break;

                case CUSTOMCC_SET_X:
                    pixels = str[++i];
                    break;
            }
        }
        else
            break;
    }
    int tiles = (pixels - startingPos)>> 3;
    if((pixels & 7) != 0)
        tiles +=1;
    return tiles;
}

//For strings without any control code besides the 00 FF one
int count_pixels_to_tiles_normal_string(byte *str, int startingPos)
{
    int pixels = startingPos;
    for(int i = 0;; i++)
    {
        if((str[i + 1] != 0xFF)) //The latter one is not really needed
            pixels += (m2_widths_table[0][decode_character(str[i])] & 0xFF);
        else
            break;
    }
    int tiles = (pixels - startingPos)>> 3;
    if(((pixels - startingPos) & 7) != 0)
        tiles +=1;
    return tiles;
}

byte print_character(byte chr, int x, int y)
{
    return print_character_formatted(chr, x, y, 0, 0xF);
}

byte print_character_formatted(byte chr, int x, int y, int font, int foreground)
{
    // 0x64 to 0x6C (inclusive) is YOU WON
    if ((chr >= YOUWON_START) && (chr <= YOUWON_END))
    {
        print_special_character(chr + 0xF0, x, y);
        return 8;
    }

    // 0x6D is an arrow ->
    else if (chr == ARROW)
    {
        print_special_character(ARROW + 0x30, x, y);
        return 8;
    }

    return print_character_with_callback(chr, x, y, font, foreground, vram, &get_tile_number_with_offset, *tilemap_pointer, 32, 0xC);
}

byte print_character_to_ram(byte chr, int *dest, int xOffset, int font, int foreground)
{
    return print_character_with_callback(chr, xOffset, 0, font, foreground, dest, &get_tile_number_grid, NULL, 32, 0x10);
}

// Prints a special tile. Pixels are copied to the VWF buffer.
// x, y in pixels
void print_special_character(int tile, int x, int y)
{
    // Special graphics must be tile-aligned
    x >>= 3;
    y >>= 3;
    unsigned short sourceTileIndex = tile + *tile_offset;
    unsigned short destTileIndex = get_tile_number(x, y) + *tile_offset;

    (*tilemap_pointer)[x + (y * 32)] = destTileIndex | *palette_mask;
    (*tilemap_pointer)[x + ((y + 1) * 32)] = (destTileIndex + 32) | *palette_mask;

    cpufastset(&vram[sourceTileIndex * 8], &vram[destTileIndex * 8], 8);
    cpufastset(&vram[(sourceTileIndex + 32) * 8], &vram[(destTileIndex + 32) * 8], 8);
}

// Prints a special tile. Pixels are not copied.
// x, y in pixels
void print_special_character_buffer(int tile, int x, int y)
{
    // Special graphics must be tile-aligned
    x >>= 3;
    y >>= 3;
    unsigned short sourceTileIndex = tile + *tile_offset;

    (*tilemap_pointer)[x + (y * 32)] = sourceTileIndex | *palette_mask;
    (*tilemap_pointer)[x + ((y + 1) * 32)] = (sourceTileIndex + 32) | *palette_mask;

}

// Maps a special character to the given tile coordinates. Only the tilemap is changed.
// x, y in tiles
void map_special_character(unsigned short tile, int x, int y)
{
    tile = format_tile(tile, false, false);
    (*tilemap_pointer)[x + (y * 32)] = tile;
    (*tilemap_pointer)[x + ((y + 1) * 32)] = tile + 32;
}

// Maps a tile to the given tile coordinates. Only the tilemap is changed.
// x, y in tiles
void map_tile(unsigned short tile, int x, int y)
{
    tile = format_tile(tile, false, false);
    (*tilemap_pointer)[x + (y * 32)] = tile;
}

byte print_character_with_callback(byte chr, int x, int y, int font, int foreground,
    int *dest, int (*getTileCallback)(int, int), unsigned short *tilemapPtr, int tilemapWidth, byte doubleTileHeight)
{
    int tileWidth = m2_font_widths[font];
    int tileHeight = m2_font_heights[font];
    int widths = m2_widths_table[font][chr];

    int paletteMask = *palette_mask;
    byte *glyphRows = &m2_font_table[font][chr * tileWidth * tileHeight * 8];

    int virtualWidth = widths & 0xFF;
    int leftPortionWidth = 8 - (x & 7);

    int tileX = x >> 3;
    int tileY = y >> 3;
    
    int offsetY = y & 7;
    
    if((tileY & 1) == 0 && offsetY >= doubleTileHeight - 0x8)
    {
        tileY++;
        offsetY -= (doubleTileHeight - 0x8);
    }

    int nextY = offsetY;
    for (int dTileY = 0; dTileY < tileHeight; dTileY++) // dest tile Y
    {
        int dTileX = 0;
        int renderedWidth = widths >> 8;
        offsetY = nextY;
        bool changed = false;

        while (renderedWidth > 0)
        {
            // Glue the leftmost part of the glyph onto the rightmost part of the canvas
            int tileIndex = getTileCallback(tileX + dTileX, tileY + dTileY); //get_tile_number(tileX + dTileX, tileY + dTileY) + tileOffset;
            bool availableSwap = (dTileY != (tileHeight - 1));
            int realTileIndex = tileIndex;
            bool useful = false; //Maybe we go over the maximum tile height, let's make sure the extra tile is properly set IF it's useful
            int tmpTileY = dTileY;
            int limit = ((tmpTileY + tileY) & 1) == 0 ? doubleTileHeight - 8 - 1: 7;
            int sumRemoved = 0;

            for (int row = 0; row < 8; row++)
            {
                int canvasRow = dest[(realTileIndex * 8) + ((row + offsetY - sumRemoved) & 7)];
                byte glyphRow = glyphRows[row + (dTileY * 8 * tileWidth) + (dTileX * 8)] & ((1 << leftPortionWidth) - 1);
                glyphRow <<= (8 - leftPortionWidth);

                int expandedGlyphRow = expand_bit_depth(glyphRow, foreground);
                int expandedGlyphRowMask = ~expand_bit_depth(glyphRow, 0xF);
                int tmpCanvasRow = canvasRow;
                canvasRow &= expandedGlyphRowMask;
                canvasRow |= expandedGlyphRow;
                
                if(!availableSwap && ((row + offsetY) >> 3) == 1 && canvasRow != tmpCanvasRow) //This changed the canvas, then it's useful... IF it's the extra vertical tile
                    useful = true;

                dest[(realTileIndex * 8) + ((row + offsetY - sumRemoved) & 7)] = canvasRow;

                if((row + offsetY - sumRemoved) == limit)
                {
                    realTileIndex = getTileCallback(tileX + dTileX, tileY + tmpTileY + 1);
                    tmpTileY++;
                    sumRemoved += limit + 1;
                    if(!changed)
                    {
                        nextY = (nextY + limit + 1) & 7;
                        changed = true;
                    }
                    limit = ((tmpTileY + tileY) & 1) == 0 ? doubleTileHeight - 8 - 1: 7;
                        
                }
            }

            if (tilemapPtr != NULL)
            {
                tilemapPtr[tileX + dTileX + ((tileY + dTileY) * tilemapWidth)] = paletteMask | tileIndex;
                if(useful)
                    tilemapPtr[tileX + dTileX + ((tileY + dTileY + 1) * tilemapWidth)] = paletteMask | realTileIndex;
            }

            if (renderedWidth - leftPortionWidth > 0 && leftPortionWidth < 8)
            {
                // Glue the rightmost part of the glyph onto the leftmost part of the next tile
                // on the canvas
                tileIndex = getTileCallback(tileX + dTileX + 1, tileY + dTileY); //get_tile_number(tileX + dTileX + 1, tileY + dTileY) + tileOffset;
                availableSwap = (dTileY != (tileHeight - 1));
                realTileIndex = tileIndex;
                useful = false; //Maybe we go over the maximum tile height, let's make sure the extra tile is properly set IF it's useful
                tmpTileY = dTileY;
                limit = ((tmpTileY + tileY) & 1) == 0 ? doubleTileHeight - 8 - 1: 7;
                sumRemoved = 0;

                for (int row = 0; row < 8; row++)
                {
                    int canvasRow = dest[(realTileIndex * 8) + ((row + offsetY - sumRemoved) & 7)];
                    byte glyphRow = glyphRows[row + (dTileY * 8 * tileWidth) + (dTileX * 8)] >> leftPortionWidth;

                    int expandedGlyphRow = expand_bit_depth(glyphRow, foreground);
                    int expandedGlyphRowMask = ~expand_bit_depth(glyphRow, 0xF);
                    int tmpCanvasRow = canvasRow;
                    canvasRow &= expandedGlyphRowMask;
                    canvasRow |= expandedGlyphRow;
                    
                    if(!availableSwap && ((row + offsetY) >> 3) == 1 && canvasRow != tmpCanvasRow) //This changed the canvas, then it's useful... IF it's the extra vertical tile
                        useful = true;

                    dest[(realTileIndex * 8) + ((row + offsetY - sumRemoved) & 7)] = canvasRow;
                    
                    if((row + offsetY - sumRemoved) == limit)
                    {
                        realTileIndex = getTileCallback(tileX + 1 + dTileX, tileY + tmpTileY + 1);
                        tmpTileY++;
                        sumRemoved += limit + 1;
                        if(!changed)
                        {
                            nextY = (nextY + limit + 1) & 7;
                            changed = true;
                        }
                        limit = ((tmpTileY + tileY) & 1) == 0 ? doubleTileHeight - 8 - 1: 7;
                    }
                }
                
                if (tilemapPtr != NULL)
                {
                    tilemapPtr[tileX + dTileX + 1 + ((tileY + dTileY) * tilemapWidth)] = paletteMask | tileIndex ;
                    if(useful)
                        tilemapPtr[tileX + dTileX + 1 + ((tileY + dTileY + 1) * tilemapWidth)] = paletteMask | realTileIndex;
                }
            }
            
            renderedWidth -= 8;
            dTileX++;
        }
    }

    return virtualWidth;
}

int convert_tile_address_buffer(int tile)
{
    //Allows using tiles with different size to make the buffer smaller
    tile -= *tile_offset;
    int inner_line = tile & 0x3F;
    int lines = (tile >> 6) << 5;
    int returned_address = ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER) + (lines * DEFAULT_DOUBLE_TILE_HEIGHT);
    
    if(inner_line >= 0x20)
        return returned_address + (0x20 * 8) + ((inner_line & 0x1F) * (DEFAULT_DOUBLE_TILE_HEIGHT - 8));
    return returned_address + (inner_line * 8);
}

byte print_character_with_callback_1bpp_buffer(byte chr, int x, int y, byte *dest, int (*getTileCallback)(int, int), int font,
    unsigned short *tilemapPtr, int tilemapWidth, byte doubleTileHeight)
{
    int tileWidth = m2_font_widths[font];
    int tileHeight = m2_font_heights[font];
    int widths = m2_widths_table[font][chr];

    int paletteMask = *palette_mask;
    byte *glyphRows = &m2_font_table[font][chr * tileWidth * tileHeight * 8];

    int virtualWidth = widths & 0xFF;

    int tileX = x >> 3;
    int tileY = y >> 3;
    
    int offsetY = y & 7;
    int offsetX = x & 7;
    
    if((tileY & 1) == 0 && offsetY >= doubleTileHeight - 0x8)
    {
        tileY++;
        offsetY -= (doubleTileHeight - 0x8);
    }

    int nextY = offsetY;
    for (int dTileY = 0; dTileY < tileHeight; dTileY++) // dest tile Y
    {
        int dTileX = 0;
        int renderedWidth = widths >> 8;
        offsetY = nextY;
        bool changed = false;

        while (renderedWidth > 0)
        {
            // Glue the leftmost part of the glyph onto the rightmost part of the canvas
            int tileIndex = get_tile_number_with_offset_buffer(tileX + dTileX, tileY + dTileY);
            int tileIndexRight = get_tile_number_with_offset_buffer(tileX + dTileX + 1, tileY + dTileY);
            bool availableSwap = (dTileY != (tileHeight - 1));
            int realTileIndex = tileIndex;
            int realTileIndexRight = tileIndexRight;
            bool useful = false; //Maybe we go over the maximum tile height, let's make sure the extra tile is properly set IF it's useful
            int tmpTileY = dTileY;
            int limit = ((tmpTileY + tileY) & 1) == 0 ? doubleTileHeight - 8 - 1: 7;
            int sumRemoved = 0;

            for (int row = 0; row < 8; row++)
            {
                unsigned short canvasRow = dest[convert_tile_address_buffer(realTileIndex) + ((row + offsetY - sumRemoved) & 7)] + (dest[convert_tile_address_buffer(realTileIndexRight) + ((row + offsetY - sumRemoved) & 7)]  << 8);
                unsigned short glyphRow = glyphRows[row + (dTileY * 8 * tileWidth) + (dTileX * 8)] << offsetX;

                unsigned short tmpCanvasRow = canvasRow;
                canvasRow |= glyphRow;
                
                if(!availableSwap && ((row + offsetY) >> 3) == 1 && canvasRow != tmpCanvasRow) //This changed the canvas, then it's useful... IF it's the extra vertical tile
                    useful = true;

                dest[convert_tile_address_buffer(realTileIndex) + ((row + offsetY - sumRemoved) & 7)] = canvasRow;
                dest[convert_tile_address_buffer(realTileIndexRight) + ((row + offsetY - sumRemoved) & 7)] = canvasRow >> 8;

                if((row + offsetY - sumRemoved) == limit)
                {
                    tmpTileY++;
                    realTileIndex = get_tile_number_with_offset_buffer(tileX + dTileX, tileY + tmpTileY);
                    realTileIndexRight = get_tile_number_with_offset_buffer(tileX + 1 + dTileX, tileY + tmpTileY);
                    sumRemoved += limit + 1;
                    if(!changed)
                    {
                        nextY = (nextY + limit + 1) & 7;
                        changed = true;
                    }
                    limit = ((tmpTileY + tileY) & 1) == 0 ? doubleTileHeight - 8 - 1: 7;
                        
                }
            }

            if (tilemapPtr != NULL)
            {
                tilemapPtr[tileX + dTileX + ((tileY + dTileY) * tilemapWidth)] = paletteMask | getTileCallback(tileX + dTileX, tileY + dTileY);
                if(renderedWidth - 8 + offsetX > 0 && offsetX != 0)
                    tilemapPtr[tileX + dTileX + 1 + ((tileY + dTileY) * tilemapWidth)] = paletteMask | getTileCallback(tileX + dTileX + 1, tileY + dTileY);
                if(useful)
                {
                    tilemapPtr[tileX + dTileX + ((tileY + tmpTileY) * tilemapWidth)] = paletteMask | getTileCallback(tileX + dTileX, tileY + tmpTileY);
                    if(renderedWidth - 8 + offsetX > 0 && offsetX != 0)
                        tilemapPtr[tileX + dTileX + 1 + ((tileY + tmpTileY) * tilemapWidth)] = paletteMask | getTileCallback(tileX + dTileX + 1, tileY + tmpTileY);
                }
            }
            renderedWidth -= 8;
            dTileX++;
        }
    }

    return virtualWidth;
}

int print_window_header_string(int *dest, byte *str, int x, int y)
{
    int pixelX = x & 7;
    int *destOffset = dest + ((x & ~7) + (y * 32));

    for (;;)
    {
        byte code = *(str + 1);
        if (code == 0xFF)
        {
            if (*str == 0)
                break;

            str += 2;
            continue;
        }

        pixelX += print_character_to_ram(decode_character(*str++), destOffset, pixelX, 4, 0xF);
    }

    return pixelX - (x & 7);
}

void clear_window_header(int *dest, int length, int x, int y)
{
    dest += (x + (y * 32)) * 8;
    clear_rect_ram(dest, length, WINDOW_HEADER_BG);
}

//Prints the number header string in a tiny buffer and stores it in one go
int print_window_number_header_string(int *dest, byte *str, int x, int y)
{
    int pixelX = x & 7;
    int *destOffset = dest + ((x & ~7) + (y * 32));
    int buffer[8 * 3];
    
    for(int i = 0; i < 8 * 3; i++)
        buffer[i] = 0x33333333;

    for (;;)
    {
        byte code = *(str + 1);
        if (code == 0xFF)
        {
            if (*str == 0)
                break;

            str += 2;
            continue;
        }

        pixelX += print_character_to_ram(decode_character(*str++), buffer, pixelX, 4, 0xF);
    }
    
    for(int i = 0; i < 8 * 3; i++)
        destOffset[i] = buffer[i];

    return pixelX - (x & 7);
}

unsigned short* print_equip_header(int type, unsigned short *tilemap, unsigned int *dest, WINDOW *window)
{
    byte *str = 0;

    switch (type)
    {
        case 3:
            str = m12_other_str5; // Weapon
            break;
        case 4:
            str = m12_other_str6; // Body
            break;
        case 5:
            str = m12_other_str7; // Arms
            break;
        case 6:
            str = m12_other_str8; // Other
            break;
    }

    if (str != 0)
    {
        int startX = WINDOW_HEADER_X * 8;
        int startY = WINDOW_HEADER_Y * 8;
        int width = 0;

        width += print_window_header_string(dest, str, startX + width, startY);

        // Print (X)
        if (window->cursor_x > 6)
        {
            int buffer[8 * 3];
            int mask = WINDOW_HEADER_BG;
            for(int i = 0; i < (width & 7); i++) //Saves only the important pixels and deletes the rest in the buffer
                mask = (mask << 4) | 0xF;
            int *destOffset = dest + (((startX + width) & ~7) + (startY * 32));
            
            for(int i = 0; i < 8; i++)
                buffer[i] = destOffset[i] & mask;
            for(int i = 0; i < 8 * 2; i++)
                buffer[8 + i] = WINDOW_HEADER_BG;
            int base = window->cursor_x_base;
            str = m2_strlookup(m2_misc_offsets, m2_misc_strings, base + 0x8C);
            width += print_window_header_string(buffer, str, width & 7, 0);
            for(int i = 0; i < 8 * 3; i++) //Restore the vram
                destOffset[i] = buffer[i];
        }

        // Update tilemap
        int tiles = (width + 7) >> 3;
        int tileIndex = WINDOW_HEADER_TILE + *tile_offset;

        for (int i = 0; i < tiles; i++)
        {
            *tilemap++ = tileIndex++ | *palette_mask;
        }
    }

    return tilemap;
}

// Returns a formatted tile value including the palette index, flip flags and
// tile offset.
unsigned short format_tile(unsigned short tile, bool flip_x, bool flip_y)
{
    tile += *tile_offset;
    tile |= *palette_mask;

    if (flip_x)
    {
        tile |= (1 << 10);
    }

    if (flip_y)
    {
        tile |= (1 << 11);
    }

    return tile;
}

// Copy party character name to window header
// Assumes that the party character names have already been rendered
// to VRAM; this just copies the map data
void copy_name_header(WINDOW *window, int character_index)
{
    // Coordinates of the name tiles
    int x = window->window_x + 1;
    int y = window->window_y - 1;

    // Print the partial border tile before the name
    (*tilemap_pointer)[x - 1 + (y * 32)] = format_tile(0xB3, false, false);

    // Get name width in pixels
    byte *name_str = pc_names + (character_index * 7);
    unsigned short *widths_table = m2_widths_table[4]; // small font
    int width = 0;

    while (*(name_str + 1) != 0xFF)
    {
        width += widths_table[decode_character(*name_str)] & 0xFF;
        name_str++;
    }

    // Print name
    int num_tiles = (width + 7) >> 3;
    int tile = name_header_tiles[character_index];

    for (int i = 0; i < num_tiles; i++)
    {
        (*tilemap_pointer)[x + i + (y * 32)] = format_tile(tile, false, false);
        tile++;
    }

    // Print flipped partial border tile after name
    (*tilemap_pointer)[x + num_tiles + (y * 32)] = format_tile(0xB3, true, false);
}

// Clears a window's name header by printing border tiles in the slots for the
// name, plus one tile on either side for the partial borders
void clear_name_header(WINDOW* window)
{
    // We don't need to know how long the name is; just make a conservative
    // estimate that it couldn't have been more than 4 tiles wide

    int x = window->window_x; // start of partial border tile
    int y = window->window_y - 1;
    int tile = format_tile(0x96, false, true);

    for (int i = 0; i < 6; i++)
    {
        (*tilemap_pointer)[x + (y * 32)] = tile;
        x++;
    }
}

// Draws the arrow tiles on a window
// The big flag controls what size to use
void draw_window_arrows(WINDOW *window, bool big)
{
    int x = window->window_x + window->window_width - 3;
    int y = window->window_y - 1;
    unsigned short tile = format_tile(big ? 0x9B : 0xBB, false, false);
    (*tilemap_pointer)[x + (y * 32)] = tile;
    (*tilemap_pointer)[x + 1 + (y * 32)] = tile + 1;
}

// Replaces window arrow tiles with regular border tiles
void clear_window_arrows(WINDOW *window)
{
    int x = window->window_x + window->window_width - 3;
    int y = window->window_y - 1;
    unsigned short tile = format_tile(0x96, false, true);
    (*tilemap_pointer)[x + (y * 32)] = tile;
    (*tilemap_pointer)[x + 1 + (y * 32)] = tile;
}

// Confirms the text breaks the boundaries
bool text_overflows_window(WINDOW* window)
{
    return (window->text_x > window->window_width) || ((window->text_x == window->window_width) && (window->pixel_x > 0));
}

void weld_entry(WINDOW *window, byte *str)
{
    weld_entry_custom(window, str, 0, 0xF);
}

int weld_entry_saturn(WINDOW *window, byte *str)
{
    weld_entry_custom(window, str, 1, 0xF);

    // TODO: figure out when the original routine at 80ED770 might return non-zero
    // Looking at 80CA3A4, maybe 1 is returned if a non-saturn glyph is encountered?
    // And looking at 80D2F24, that seems to be the case...
    return 0;
}

void weld_entry_custom(WINDOW *window, byte *str, int font, int foreground)
{
    int chr = decode_character(*str);

    int x = window->pixel_x + (window->window_x + window->text_x) * 8;
    int y = (window->window_y + window->text_y) * 8;

    if(!window->inside_width_calc)
        x += print_character_formatted(chr, x, y, font, foreground);
    else
        x += (m2_widths_table[font][chr] & 0xFF);
        
    window->pixel_x = x & 7;
    window->text_x = (x >> 3) - window->window_x;
    if(window->inside_width_calc && text_overflows_window(window))
        window->text_x = window->window_width + 1;
}

// Returns: ____XXXX = number of characters printed
//          XXXX____ = number of pixels printed
// x, y: pixels
int print_string(byte *str, int x, int y)
{
    if (str == NULL)
        return 0;

    byte chr;
    int initial_x = x;
    int charCount = 0;

    while (str[1] != 0xFF)
    {
        x += print_character(decode_character(*str++), x, y);
        charCount++;
    }

    int totalWidth = x - initial_x;

    return (charCount & 0xFFFF) | (totalWidth << 16);
}

// Edited version which recognizes the 5F FF code
// Returns: ____XXXX = number of characters printed
//          XXXX____ = number of pixels printed
// x, y: pixels
int print_string_edited(byte *str, int x, int y)
{
    if (str == NULL)
        return 0;

    byte chr;
    int initial_x = x;
    int charCount = 0;

    while (!(str[1] == 0xFF && str[0] == 0x00))
    {
        if(str[1] != 0xFF)
        {
            x += print_character(decode_character(*str++), x, y);
            charCount++;
        }
        else if(str[0] == 0x5F)
        {
            x = initial_x + str[2];
            str += 3;
        }
    }

    int totalWidth = x - initial_x;

    return (charCount & 0xFFFF) | (totalWidth << 16);
}

unsigned short printstr_hlight_edited(WINDOW* window, byte* str, unsigned short x, unsigned short y, bool highlight)
{
    return printstr_hlight_pixels_edited(window, str, x << 3, y << 4, highlight);
}

unsigned short printstr_hlight_pixels_edited(WINDOW* window, byte* str, unsigned short x, unsigned short y, bool highlight)
{
    unsigned short printX = x + ((window->window_x) << 3);
    unsigned short printY = y + ((window->window_y) << 3);
    unsigned short tmpPaletteMsk = (*palette_mask);
    unsigned short palette_mask_highlight = tmpPaletteMsk;
    if(highlight)
        palette_mask_highlight += 0x1000;
    (*palette_mask) = palette_mask_highlight;
    
    unsigned short printed_Characters = print_string_edited(str, printX, printY);
    
    (*palette_mask) = tmpPaletteMsk;
    
    return printed_Characters;
}

int print_menu_string(WINDOW* window)
{
    byte *menu_text = window->menu_text;
    if (menu_text == NULL)
        return 0;

    int x = window->window_x << 3;
    int y = (window->window_y + window->text_y) << 3;

    byte chr;
    int initial_x = x;
    int charCount = 0;
    bool looping = true;
    int set_count = 0;
    byte first_set_value = 0;

    while (looping)
    {
        if (menu_text[1] == 0xFF)
        {
            switch (menu_text[0])
            {
                case CUSTOMCC_SET_X:
                {
                    byte set_value = menu_text[2];
                    x = set_value + initial_x;
                    menu_text += 3;

                    set_count++;
                    if (set_count == 1)
                    {
                        first_set_value = set_value;
                    }
                    else if (set_count == 2)
                    {
                        // If we're calling SET the second time, update the
                        // window cursor delta to be the difference between
                        // the two set values
                        window->cursor_x_delta = (set_value - first_set_value) >> 3;
                    }

                    break;
                }
                case CUSTOMCC_ADD_X:
                    x += menu_text[2];
                    menu_text += 3;
                    break;
                default:
                    looping = false;
                    window->menu_text = NULL; //Otherwise it will keep printing indefinetly
                    break;
            }
        }
        else
        {
            x += print_character(decode_character(*menu_text++), x, y);
            charCount++;
        }
    }

    window->text_x = 0;
    window->pixel_x = 0;

    int totalWidth = x - initial_x;

    return (charCount & 0xFFFF) | (totalWidth << 16);
}

// x,y: tile coordinates
void clear_tile(int x, int y, int pixels)
{
    // Clear pixels
    int tileIndex = get_tile_number(x, y) + *tile_offset;
    cpufastset(&pixels, &vram[tileIndex * 8], CPUFASTSET_FILL | 8);

    // Reset the tilemap (e.g. get rid of equip or SMAAAASH!! tiles)
    (*tilemap_pointer)[x + (y * 32)] = tileIndex | *palette_mask;
}

// x,y: tile coordinates
void clear_rect(int x, int y, int width, int height, int pixels)
{
    for (int tileY = 0; tileY < height; tileY++)
    {
        for (int tileX = 0; tileX < width; tileX++)
        {
            clear_tile(x + tileX, y + tileY, pixels);
        }
    }
}

void clear_rect_ram(int *dest, int tileCount, int pixels)
{
    cpufastset(&pixels, dest, CPUFASTSET_FILL | (tileCount * 8));
}

void clear_window(WINDOW *window)
{
    clear_rect(window->window_x, window->window_y,
        window->window_width, window->window_height,
        WINDOW_AREA_BG);
}

// x, y, width: tile coordinates
void print_blankstr(int x, int y, int width)
{
    clear_rect(x, y, width, 2, WINDOW_AREA_BG);
}

// x, y, width: tile coordinates
void print_blankstr_window(int x, int y, int width, WINDOW* window)
{
    print_blankstr(x + window->window_x, y + window->window_y, width);
}

// x,y: tile coordinates
void copy_tile(int xSource, int ySource, int xDest, int yDest)
{
    int sourceTileIndex = get_tile_number(xSource, ySource) + *tile_offset;
    int destTileIndex = get_tile_number(xDest, yDest) + *tile_offset;
    cpufastset(&vram[sourceTileIndex * 8], &vram[destTileIndex * 8], 8);
}

// x,y: tile coordinates
void copy_tile_up(int x, int y)
{
    copy_tile(x, y, x, y - 2);
}

void print_space(WINDOW *window)
{
    byte space = SPACE;
    weld_entry(window, &space);
}

// Prints the dollar sign, the zeroes, and (optionally if style == 1) the 00 symbol
void print_number_menu(WINDOW* window, int style)
{
    // Print a $ sign (0x54) at (32, 32) pixels
    int x = (window->window_x << 3) + 32;
    int y = (window->window_y << 3) + 32;

    print_character(decode_character(0x54), x, y);
    x += 8;

    // Print the zeroes (0x60)
    for (int i = 0; i < window->cursor_x_delta; i++)
    {
        print_character(decode_character(0x60), x, y);
        x += 8;
    }

    // Print the 00 symbol (0x56)
    if (style == 1)
    {
        print_character(decode_character(0x56), x, y);
    }
}

// Print the given digit for the number selection menu at the current cursor location
void print_number_menu_current(byte digit, WINDOW* window)
{
    // Skip the 4 blank tiles
    int x = (window->window_x + (window->cursor_x_delta - window->cursor_x) + 4) << 3;

    // Skip the first two text rows
    int y = (window->window_y + 4) << 3;

    // Erase what was there before
    print_blankstr(x >> 3, y >> 3, 1);

    // Now print the digit
    print_character(decode_character(digit + 0x60), x, y);
}

// Clears the number menu of a window
// More specifically, clear the 3rd row of text and reset the bottom window border
void clear_number_menu(WINDOW* window)
{
    // Clear the text
    print_blankstr_window(0, 4, window->window_width, window);

    // Reset the border (6th tile row)
    unsigned short border_tile = (*tile_offset + 0x96) | *palette_mask;
    for (int i = 0; i < window->window_width; i++)
    {
        (*tilemap_pointer)[window->window_x + i + ((window->window_y + 6) * 32)] = border_tile;
    }
}

// Prints a character to a window, and updates the window's text position
byte print_character_to_window(byte chr, WINDOW* window)
{
    int x = ((window->window_x + window->text_x) << 3) + window->pixel_x;
    int y = (window->window_y + window->text_y) << 3;

    byte width = print_character(chr, x, y);
    x += width;

    window->pixel_x = x & 7;
    window->text_x = (x >> 3) - window->window_x;

    return width;
}

// Write the following, in sequence, to str:
// [5F FF xx] code to right-align the text to padding pixels
// Dollar sign (0x54)
// Digits
// 00 symbol (0x56)
// [00 FF] end code
void format_cash_window(int value, int padding, byte* str)
{
    // Convert digits to BCD for easy parsing
    int digit_count;
    int bcd = bin_to_bcd(value, &digit_count);

    // Dollar sign is 6 pixels wide, 00 symbol is 8
    padding -= 14;

    // Subtract 6 pixels for each digit
    padding -= (6 * digit_count);

    // Control code
    *str++ = 0x5F;
    *str++ = 0xFF;
    *str++ = padding & 0xFF;

    *str++ = 0x54;

    // Write the digits
    for (int i = 0; i < digit_count; i++)
    {
        byte digit = ((bcd >> ((digit_count - 1 - i) * 4)) & 0xF) + ZERO;
        *str++ = digit;
    }

    *str++ = 0x56;
    *str++ = 0;
    *str++ = 0xFF;
}

int player_name_printing_registration(byte* str, WINDOW* window)
{
    char String[26];
    bool ended = false;
    int total = 24;
    for(int i = 0; i < 24; i++)
    {
        if(ended)
        {
            String[i] = 0x53;
        }
        else if((i < 23 && str[i + 1] == 0xFF && str[i] == 0) || (i == 23 && str[i] == 0))
        {
            String[i] = 0x70;
            total = i;
            ended = true;
        }
        else
            String[i] = str[i];
    }
    String[24] = 0;
    String[25] = 0xFF;
    print_blankstr_window_buffer(0, 2, 24, window);
    printstr_buffer(window, String, 0, 1, 0);
    return total;
}

// The game draws windows lazily: no window will be drawn to the screen until
// a renderable token is encountered. So it's possible to have text that
// does stuff in the background without ever showing a window. Lots of doors
// and hotspots do this for example.
// When the game first encounters a renderable token, it checks two things:
// - If the flag at 0x30051F0 is 1, then call m2_resetwindow and set the flag to 0
// - If the window has flag 0x20 set, then call m2_drawwindow (which unsets the
//   window flag)
// See 80CA2C2 for an example. We want to replicate this behaviour sometimes,
// e.g. for custom control codes that are considered renderable.
void handle_first_window(WINDOW* window)
{
    if (*first_window_flag == 1)
    {
        m2_resetwindow(window, false);
        *first_window_flag = 0;
    }
    else if (window->redraw)
    {
        m2_drawwindow(window);
    }
}

//Returns in *String a string containing "Talk to" and "Goods"
void setupShortMainMenu_Talk_to_Goods(char *String)
{
    char Talk_to[] = "Talk to";
    char Goods[] = "Goods";
    int index = 0;
    String[index++] = 0x5F;
    String[index++] = 0xFF;
    String[index++] = 0x08;
    for(int i = 0; i < (sizeof(Talk_to) - 1); i++)
        String[index++] = encode_ascii(Talk_to[i]);
    String[index++] = 0x5F;
    String[index++] = 0xFF;
    String[index++] = 0x30;
    for(int i = 0; i < (sizeof(Goods) - 1); i++)
        String[index++] = encode_ascii(Goods[i]);
    String[index++] = 0;
    String[index++] = 0xFF;
}

int get_pointer_jump_back(byte *character)
{
    byte *address1 = ((byte*)0x3004F24);
    byte *address2 = ((byte*)0x3005078);
    byte val = (*address1);
    byte val2 = (*address2);
    if(val != 0 || val2 == 0)
        return 0;
    val2--;
    (*address2) = val2;
    int *address3 = ((int*)0x3005080);
    byte *str = (byte*)(*(address3 + val2));
    return (str - character - 2);
}

void print_letter_in_buffer(WINDOW* window, byte* character, byte* dest)
{
    m2_cstm_last_printed[0] = (*character);
    weld_entry_custom_buffer(window, character, 0, 0xF, dest);
    if(window->delay_between_prints == 0)
        return;
    byte* address = (byte*)0x3005218;
    byte counter = (*address);
    if(counter == 1)
        m2_soundeffect(0x133);
}

int print_window_with_buffer(WINDOW* window)
{
    int delay = 0;
    if((window->loaded_code != 0) && ((*script_readability) == 0))
    {
        if(window->delay_between_prints == 0)
            delay = 0;
        else if(!window->enable && window->flags_unknown1 == 1)
        {
            
        }
        else if(window->enable && window->flags_unknown1 == 1)
        {
            
        }
        else
            return 0;
        while(window->loaded_code !=0)
        {
            window->delay = delay;
            print_character_with_codes(window, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
        }
    }
    return 0;
}

void scrolltext_buffer(WINDOW* window, byte* dest)
{
    unsigned short empty_tile = ((*tile_offset) + 0x1FF) | (*palette_mask);
    unsigned short *arrangementBase = (*tilemap_pointer);
    int start = (window->window_y * 32) + window->window_x;
    if(window->window_height <= 2)
    {
        if(window->window_area > 0)
        {
            for(int y = 0; y  < window->window_height && y + window->window_y <= 0x1F; y++)
                for(int x = 0; x < window->window_width && x + window->window_x <= 0x1F; x++)
                {
                    arrangementBase[start + x + (y * 32)] = empty_tile;
                    clear_tile_buffer(x + window->window_x, y + window->window_y, dest);
                }
        }
    }
    
    if(window->window_area > 0)
    {
        for(int y = 2; y  < window->window_height && y + window->window_y <= 0x1F; y++)
                for(int x = 0; x < window->window_width && x + window->window_x <= 0x1F; x++)
                {
                    if(arrangementBase[start + ((y - 2) * 32) + x] == empty_tile)
                    {
                        if(arrangementBase[start + (y * 32) + x] != empty_tile) //Non Blank to Blank
                        {
                            copy_tile_up_buffer(x, y, dest);
                            arrangementBase[start + ((y - 2) * 32) + x] = arrangementBase[start + (y * 32) + x];
                        }
                    }
                    else
                    {
                        if(arrangementBase[start + (y * 32) + x] == empty_tile) //Blank to Non Blank
                        {
                            arrangementBase[start + ((y - 2) * 32) + x] = empty_tile;
                        }
                        copy_tile_up_buffer(x, y, dest); //Non Blank to Non Blank
                    }
                    
                }
    }
    
    for(int y = window->window_height - 2; y >= 0 && y < window->window_height; y++)
        for(int x = 0; x < window->window_width && x + window->window_x <= 0x1F; x++)
        {
            arrangementBase[start + x + (y * 32)] = empty_tile;
            clear_tile_buffer(x + window->window_x, y + window->window_y, dest);
        }
}

void properScroll(WINDOW* window, byte* dest)
{
    scrolltext_buffer(window, dest);
    window->text_y = window->text_y - 2;
    window->text_x = 0;
    window->pixel_x = 0;
}

void setStuffWindow_Graphics()
{
    int *something = (int*)0x3005220;
    unsigned short *address = (unsigned short*)((*something) + 0x4BA);
    (*address) = 0;
    (*(address + 2)) = 0;
    (*(address - 2)) = (*(address - 2)) + 1;
    if((*(address - 2)) >= 2)
        (*(address + 2)) = 0;
}

byte print_character_with_codes(WINDOW* window, byte* dest)
{
    int delay = window->delay--;
    if(delay > 0)
        return 0;
    bool usingOffset2= false;
    int offsetJump = 0;
    int returnedLength = 0;
    byte *character = window->text_start + window->text_offset;    
    int y = window->window_y + window->text_y;
    int x = window->window_x + window->text_x;
    unsigned short *tilesetDest = (unsigned short *)(*tilemap_pointer);
    tilesetDest = tilesetDest + (y << 6) + (x << 1);
    
    
    if((*(character + 1)) != 0xFF)
        if(window->text_y >= window->window_height || y > 0x1F)
        {
            properScroll(window, dest);
            return 0;
        }
    
    switch(window->loaded_code)
    {
        case 6:
        break;
        case 7:
            character = m2_ness_name + window->text_offset2; //Ness
            usingOffset2 = true;
        break;
        case 8:
            character = (m2_ness_name + 7) + window->text_offset2; //Paula
            usingOffset2 = true;
        break;
        case 9:
            character = (m2_ness_name + 14) + window->text_offset2; //Jeff
            usingOffset2 = true;
        break;
        case 10:
            character = (m2_ness_name + 21) + window->text_offset2; //Poo
            usingOffset2 = true;
        break;
        case 11:
            character = (m2_ness_name + 36) + window->text_offset2; //Food
            usingOffset2 = true;
        break;
        case 12:
            character = (m2_ness_name + 44) + window->text_offset2; //Rockin
            usingOffset2 = true;
        break;
        case 20:
            character = (m2_ness_name + 28) + window->text_offset2; //King
            usingOffset2 = true;
        break;
        case 13:
        break; //User
        case 14:
        break; //Target
        default:
        break;
    }
    
    if((*(character + 1)) == 0xFF)
    {
        byte code = (*character);
        switch(code)
        {
            case 1:
                window->text_y += 2;
                window->text_x = 0;
                window->pixel_x = 0;
                if(!usingOffset2)
                    window->text_offset += 2;
                else
                    window->text_offset2 += 2;
                setStuffWindow_Graphics();
                if(window->text_y >= window->window_height || window->text_y + window->window_y > 0x1F)
                    properScroll(window, dest);
            break;
            case 2:
                window->text_y += 2;
                window->text_x = 0;
                window->pixel_x = 0;
                if(!usingOffset2)
                    window->text_offset += 2;
                else
                    window->text_offset2 += 2;
                window->counter = 0;
                window->loaded_code = 2;
                setStuffWindow_Graphics();
                return 2;
            case 0xD:
            case 0xE:
            case 0xF:
            case 0x10:
                window->text_offset += 2;
                window->text_offset2 = 0;
                window->loaded_code = 7 + code - 0xD;
            break;
            case 0:
            if(window->loaded_code >= 6 && window->loaded_code <= 0x20)
            {
                window->loaded_code = 1;
                return 1;
            }
            else
            {
                offsetJump = get_pointer_jump_back(character);
                if(offsetJump != 0)
                    window->text_offset += 2 + offsetJump;
                else
                {
                    window->loaded_code = 0;
                    return 1;
                }
            }
            break;
            default:
                if(code >= 0x60)
                    window->text_offset += m2_jump_to_offset(character);
                else
                {
                    returnedLength = custom_codes_parse_generic(code, character, window, dest);
                    if(returnedLength == 0)
                        returnedLength = 2;
                    else if(returnedLength < 0)
                        returnedLength = 0;
                    window->text_offset += returnedLength;
                }
            break;
        }
    }
    else
    {
        handle_first_window_buffer(window, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
        window->delay = window->delay_between_prints;
        if(window->text_x >= window->window_width || (window->text_x + window->window_x) > 0x1F)
        {
            window->text_y += 2;
            window->text_x = 0;
            window->pixel_x = 0;
        }
        print_letter_in_buffer(window, character, dest);
        if(!usingOffset2)
            window->text_offset += 1;
        else
            window->text_offset2 += 1;
    }
    return 0;
}

byte print_character_formatted_buffer(byte chr, int x, int y, int font, int foreground, byte *dest)
{
    // 0x64 to 0x6C (inclusive) is YOU WON
    if ((chr >= YOUWON_START) && (chr <= YOUWON_END))
    {
        print_special_character_buffer(chr + 0xF0, x, y);
        return 8;
    }

    // 0x6D is an arrow ->
    if (chr == ARROW)
    {
        print_special_character_buffer(ARROW + 0x30, x, y);
        return 9;
    }

    return print_character_with_callback_1bpp_buffer(chr, x, y, dest, &get_tile_number_with_offset, font, *tilemap_pointer, 32, 0xC);
}

void weld_entry_custom_buffer(WINDOW *window, byte *str, int font, int foreground, byte* dest)
{
    int chr = decode_character(*str);

    int x = window->pixel_x + (window->window_x + window->text_x) * 8;
    int y = (window->window_y + window->text_y) * 8;

    if(!window->inside_width_calc)
        x += print_character_formatted_buffer(chr, x, y, font, foreground, dest);
    else
        x += (m2_widths_table[font][chr] & 0xFF);

    window->pixel_x = x & 7;
    window->text_x = (x >> 3) - window->window_x;
    if(window->inside_width_calc && text_overflows_window(window))
        window->text_x = window->window_width + 1;
        
}

void handle_first_window_buffer(WINDOW* window, byte* dest)
{
    if (*first_window_flag == 1)
    {
        buffer_reset_window(window, false, dest);
        *first_window_flag = 0;
    }
    else if (window->redraw)
    {
        window->pixel_x = 0;
        buffer_drawwindow(window, dest);
    }
}

void clear_window_buffer(WINDOW *window, byte* dest)
{
    clear_rect_buffer(window->window_x, window->window_y,
        window->window_width, window->window_height,
        dest);
}

// x,y: tile coordinates
void clear_rect_buffer(int x, int y, int width, int height, byte* dest)
{
    for (int tileY = 0; tileY < height; tileY++)
    {
        for (int tileX = 0; tileX < width; tileX++)
        {
            clear_tile_buffer(x + tileX, y + tileY, dest);
        }
    }
}

int print_string_in_buffer(byte *str, int x, int y, byte *dest)
{
    if (str == NULL)
        return 0;

    byte chr;
    int initial_x = x;
    int charCount = 0;

    while (str[1] != 0xFF || str[0] != 0)
    {
        if(str[1] == 0xFF && str[0] == 1)
        {
            x = initial_x; 
            str += 2;
            y+= 0x10;
        }
        else if(str[1] != 0xFF)
        {
            x += print_character_formatted_buffer(decode_character(*str++), x, y, 0, 0xF, dest);
            charCount++;
        }
        else
            break;
    }

    int totalWidth = x - initial_x;

    return (charCount & 0xFFFF) | (totalWidth << 16);
}

int printstr_buffer(WINDOW* window, byte* str, unsigned short x, unsigned short y, bool highlight)
{
    int tmpOffset = window->text_offset;
    int tmpOffset2 = window->text_offset2;
    byte* tmpTextStart = window->text_start;
    unsigned short tmpText_x = window->text_x;
    unsigned short tmpText_y = window->text_y;
    unsigned short tmpDelayPrints = window->delay_between_prints;
    unsigned short tmpPaletteMsk = (*palette_mask);
    unsigned short palette_mask_highlight = tmpPaletteMsk;
    window->text_start = str;
    window->text_offset = 0;
    window->text_offset2 = 0;
    window->text_x = x;
    window->text_y = (y << 1);
    if(highlight)
        palette_mask_highlight += 0x1000;
    (*palette_mask) = palette_mask_highlight;
    
    window->pixel_x = 0;
    unsigned short output = 0;
    while(output != 1)
    {
        window->delay = 0;
        output = print_character_with_codes(window, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
    }
    int retValue = (window->text_x << 3) + window->pixel_x;
    window->text_start = tmpTextStart;
    window->text_offset = tmpOffset;
    window->text_offset2 = tmpOffset2;
    window->text_x = tmpText_x;
    window->text_y = tmpText_y;
    window->delay_between_prints = tmpDelayPrints;
    (*palette_mask) = tmpPaletteMsk;
    return retValue;
}

//Instead of printing an highlighted version of the string, it just highlights the corresponding arrangements
int highlight_string(WINDOW* window, byte* str, unsigned short x, unsigned short y, bool highlight)
{
    int retValue = 0;
    if(highlight)
    {
        unsigned short palette_mask_highlight = (*palette_mask) + (highlight == true ? 0x1000 : 0);
        unsigned short* arrangementBase = (*tilemap_pointer) + window->window_x + x + ((window->window_y + (y << 1)) << 5);
        int totalTiles = count_pixels_to_tiles_normal_string(str, 0);
        for(int i = 0; i < totalTiles; i++)
        {
            arrangementBase[i] = (arrangementBase[i] & 0x0FFF) | palette_mask_highlight;
            arrangementBase[i + 0x20] = (arrangementBase[i + 0x20] & 0x0FFF) | palette_mask_highlight;
        }
        retValue = (x + totalTiles) << 3;
    }
    return retValue;
}

//Highlights "Talk to"
void highlight_talk_to()
{
    char Talk_to[] = "Talk to";
    byte str[0xA];
    int i;
    for(i = 0; i < (sizeof(Talk_to) - 1); i++)
        str[i] = encode_ascii(Talk_to[i]);
    str[i++] = 0;
    str[i] = 0xFF;
    highlight_string(getWindow(0), str, 1, 0, true);
}

unsigned short printstr_hlight_buffer(WINDOW* window, byte* str, unsigned short x, unsigned short y, bool highlight)
{
    return printstr_hlight_pixels_buffer(window, str, x << 3, y << 4, highlight);
}

unsigned short printstr_hlight_pixels_buffer(WINDOW* window, byte* str, unsigned short x, unsigned short y, bool highlight)
{
    unsigned short printX = x + ((window->window_x) << 3);
    unsigned short printY = y + ((window->window_y) << 3);
    unsigned short tmpPaletteMsk = (*palette_mask);
    unsigned short palette_mask_highlight = tmpPaletteMsk;
    if(highlight)
        palette_mask_highlight += 0x1000;
    (*palette_mask) = palette_mask_highlight;
    
    unsigned short printed_Characters = print_string_in_buffer(str, printX, printY, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
    
    (*palette_mask) = tmpPaletteMsk;
    
    return printed_Characters;
}

WINDOW* getWindow(int index)
{
    return window_pointers[index];
}

int initWindow_buffer(WINDOW* window, byte* text_start, unsigned short delay_between_prints)
{
    window->vwf_skip = false;
    window->unknown3 = 0;
    window->text_x = 0;
    window->text_y = 0;
    window->text_offset = 0;
    window->text_start = text_start;
    window->text_start2 = text_start;
    window->delay_between_prints = delay_between_prints;
    window->delay = 0;
    window->loaded_code = 1;
    window->enable = true;
    window->flags_unknown1 |= 1;
    window->redraw = true;
    if(text_start == NULL)
        buffer_drawwindow(window, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
    return 0;
}

//A different initWindow called by some windows
int initWindow_cursor_buffer(WINDOW* window, byte* text_start, unsigned short cursor_x_delta, unsigned short unknown7a, unsigned short cursor_x_base)
{
    window->vwf_skip = false;
    window->unknown3 = 0;
    window->text_x = 0;
    window->text_y = 0;
    window->text_offset = 0;
    window->text_start = text_start;
    window->text_start2 = text_start;
    window->delay_between_prints = 0;
    window->delay = 0;
    window->counter = 0;
    window->loaded_code = 1;
    if(!window->enable)
    {
        window->cursor_y = 0;
        window->unknown6 = 0;
        window->unknown6a = 0;
        window->unknown7 = 0;
        window->unknown7a = unknown7a;
        window->cursor_x = cursor_x_base;
        window->cursor_x_base = cursor_x_base;
        window->cursor_x_delta = cursor_x_delta;
    }
    window->enable = true;
    window->flags_unknown1 |= 1;
    window->redraw = true;
    if(text_start == NULL)
        buffer_drawwindow(window, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
    return 0;
}

void clearWindowTiles_buffer(WINDOW* window)
{
    clear_window_buffer(window, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
    window->text_x = 0;
    window->text_y = 0;
}

// x,y: tile coordinates
void clear_tile_buffer(int x, int y, byte* dest)
{
    // Clear pixels
    int tileIndex = get_tile_number_buffer(x, y) + *tile_offset;
    int *destTileInt = (int*)&dest[convert_tile_address_buffer(tileIndex)];
    destTileInt[0] = 0;
    if(((tileIndex - *tile_offset) & 0x20) == 0)
        destTileInt[1] = 0;

    // Reset the tilemap (e.g. get rid of equip or SMAAAASH!! tiles)
    (*tilemap_pointer)[x + (y * 32)] = (get_tile_number(x, y) + (*tile_offset)) | *palette_mask;
}

int buffer_reset_window(WINDOW* window, bool skip_redraw, byte* dest)
{
    window->delay = 0;
    byte code = window->loaded_code - 0xD;
    if(code >= 1)
        window->loaded_code = 1;
    window->enable = true;
    window->flags_unknown1 = 1;
    window->redraw = true;
    window->hold = true;
    window->flags_unknown3a = window->flags_unknown3a && 0x1C; //The first byte is set to 0x23
    if(!skip_redraw)
        buffer_drawwindow(window, dest);
    return 0;
}

int buffer_drawwindow(WINDOW* window, byte* dest)
{
    if((int*)dest == vram)
        clear_window(window);
    else
        clear_window_buffer(window, dest);
    unsigned short empty_tile = (0x1FF + (*tile_offset)) | (*palette_mask);
    int baseOfWindow = ((window->window_y - 1) * 32) + window->window_x - 1;
    unsigned short *arrangementBase = (*tilemap_pointer);
    
    for(int y = 0; y  < window->window_height && y + window->window_y <= 0x1F; y++)
        for(int x = 0; x < window->window_width && x + window->window_x <= 0x1F; x++)
            arrangementBase[baseOfWindow + 1 + x + ((y + 1) * 32)] = empty_tile;
    
    window->counter = 0;
    unsigned short void_tile = (0x1DF + (*tile_offset)) | (*palette_mask);
    if((window->window_x - 1 < 0) || (window->window_x - 1 + window->window_width > 0x1F) || (window->window_y - 1 < 0) || (window->window_y - 1 + window->window_height > 0x1F))
        return -1;

    //Tiles
    unsigned short bottom_right_corner_void = (0x93 + (*tile_offset)) | (*palette_mask);
    unsigned short bottom_right_corner_full = (0x94 + (*tile_offset)) | (*palette_mask);
    unsigned short bottom_left_corner_void = 0x400 | bottom_right_corner_void;
    unsigned short bottom_left_corner_full = 0x400 | bottom_right_corner_full;
    unsigned short top_right_corner_void = 0x800 | bottom_right_corner_void;
    unsigned short top_right_corner_full = 0x800 | bottom_right_corner_full;
    unsigned short top_left_corner_void = 0xC00 | bottom_right_corner_void;
    unsigned short top_left_corner_full = 0xC00 | bottom_right_corner_full;

    //Check which tiles to use for the corners
    unsigned short current_top_left_tile = arrangementBase[baseOfWindow];
    if(current_top_left_tile == void_tile) //Top left
        arrangementBase[baseOfWindow] = top_left_corner_void;
    else if(current_top_left_tile != top_left_corner_void)
        arrangementBase[baseOfWindow] = top_left_corner_full;
    unsigned short current_top_right_tile = arrangementBase[baseOfWindow + 1 + window->window_width];
    if(current_top_right_tile == void_tile) //Top right
        arrangementBase[baseOfWindow + 1 + window->window_width] = top_right_corner_void;
    else if(current_top_right_tile != top_right_corner_void)
        arrangementBase[baseOfWindow + 1 + window->window_width] = top_right_corner_full;
    unsigned short current_bottom_left_tile = arrangementBase[baseOfWindow + ((window->window_height + 1) * 32)];
    if(current_bottom_left_tile == void_tile) //Bottom left
        arrangementBase[baseOfWindow + ((window->window_height + 1) * 32)] = bottom_left_corner_void;
    else if(current_bottom_left_tile != bottom_left_corner_void)
        arrangementBase[baseOfWindow + ((window->window_height + 1) * 32)] = bottom_left_corner_full;
    unsigned short current_bottom_right_tile = arrangementBase[baseOfWindow + ((window->window_height + 1) * 32) + 1 + window->window_width];
    if(current_bottom_right_tile == void_tile) //Bottom right
        arrangementBase[baseOfWindow + ((window->window_height + 1) * 32) + 1 + window->window_width] = bottom_right_corner_void;
    else if(current_bottom_right_tile != bottom_right_corner_void)
        arrangementBase[baseOfWindow + ((window->window_height + 1) * 32) + 1 + window->window_width] = bottom_right_corner_full;
    
    //Border tiles
    unsigned short *address = (unsigned short*)0x3000A0E;
    unsigned short bottom_horizontal = (0x96 + (*tile_offset)) | (*palette_mask);
    unsigned short top_horizontal = 0x800 | bottom_horizontal;
    unsigned short right_vertical = (0x95 + (*tile_offset)) | (*palette_mask);
    unsigned short left_vertical = 0x400 | right_vertical;
    
    for(int i = 0; i < window->window_width; i++)
    {
        arrangementBase[baseOfWindow + 1 + i] = top_horizontal;
        arrangementBase[baseOfWindow + ((window->window_height + 1) * 32) + 1 + i] = bottom_horizontal;
    }
    
    for(int i = 0; i < window->window_height; i++)
    {
        arrangementBase[baseOfWindow + ((i + 1) * 32)] = left_vertical;
        arrangementBase[baseOfWindow + ((i + 1) * 32) + 1 + window->window_width] = right_vertical;
    }
    
    window->redraw = false;
    window->enable = true;
    
    (*address) = 1;
    return 0;
}

int setNumber_getLength(int value, byte *str, int maxLength)
{
    if(value <= 0)
    {
        str[0] = ZERO;
        return 1;
    }
    unsigned int *upperValuesTable = (unsigned int*)0x8B1C064;
    unsigned int upperValue = *(upperValuesTable + maxLength);
    if(value >= upperValue)
    {
        for(int i = 0; i < maxLength; i++)
            str[i] = ZERO + 9;
        return maxLength;
    }
    int pos = 0;
    int tmpValue = value;
    for(int i=0; i < maxLength + 1; i++)
    {
        if(value >= upperValue)
        {
            byte digit;
            if(upperValue == 0)
                digit = tmpValue;
            else
            {
                digit = m2_div(tmpValue, upperValue);
                tmpValue -= (digit * upperValue);
            }
            str[pos++] = ZERO + digit;
        }
        upperValue = *(upperValuesTable + maxLength - i - 1);
    }
    return pos;
}

unsigned short ailmentTileSetup(byte *ailmentBase, unsigned short defaultVal)
{
    int value = defaultVal;
    byte flagValue = 0;
    if((*ailmentBase) == CONSCIOUS)
    {
        if((*(ailmentBase + 1)) != CONSCIOUS)
        {
            flagValue = (*(ailmentBase + 1));
            value = 1;
        }
        else if((*(ailmentBase + 2)) != CONSCIOUS)
        {
            flagValue = (*(ailmentBase + 2));
            value = 2;
        }
        else if((*(ailmentBase + 3)))
        {
            flagValue = (*(ailmentBase + 3));
            value = 3;
        }
        else if((*(ailmentBase + 4)))
        {
            flagValue = (*(ailmentBase + 4));
            value = 4;
        }
        else if((*(ailmentBase + 5)))
        {
            flagValue = (*(ailmentBase + 5));
            value = 5;
        }
        else if((*(ailmentBase + 6)))
        {
            flagValue = (*(ailmentBase + 6));
            value = 6;
        }
        else 
            return 0;
    }
    else 
    {
        value = 0;
        flagValue = (*(ailmentBase));
    }
    unsigned short *returnValues = (unsigned short*)0x8B1F2E4;
    return (*(returnValues + (value * 7) + flagValue - 1));
}

void printTinyArrow(int x, int y)
{
    print_special_character_buffer(0x9F, x, y);
}

void printCashWindow()
{
    (*window_flags) |= 2;
    byte *str = (*free_strings_pointers);
    format_cash_window(*cash_on_hand, 0x30, str);
    initWindow_buffer(getWindow(1), str, 0);
    print_window_with_buffer(getWindow(1));
    m2_sub_d6844();
    m2_sub_d3c50();
}

void eb_cartridge_palette_change(bool background)
{
    unsigned short *paletteDest = (unsigned short*)0x5000040;
    if(background)
    {
        if(BUILD_PALETTE)
        {
            //Makes the game do the palette work. Copy the result in a bin file and use that instead in order to make the swap fast
            unsigned short palettes[0x50];
            cpuset(paletteDest, palettes, 0x50);
            for(int i = 0; i < 5; i++)
                m12_dim_palette(&palettes[i * 0x10], 0x10, 0x800);
            cpuset(palettes, paletteDest, 0x50);
        }
        else
            cpuset(m12_cartridge_palettes_dimmed, paletteDest, 0x50);
    }
    else
        cpuset(&m12_cartridge_palettes[0x20], paletteDest, 0x50);
}

// x, y, width: tile coordinates
void print_blankstr_buffer(int x, int y, int width, byte *dest)
{
    clear_rect_buffer(x, y, width, 2, dest);
}

//Function called for printing the alphabet. Seems to be a trimmed down version of the normal script-reading function. Probably done in order to make this faster
int print_alphabet_buffer(WINDOW* window)
{
    byte* dest = (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER));
    if(window->redraw)
        buffer_drawwindow(window, dest);
    if(window->loaded_code == 0 || (*script_readability) != 0)
        return 0;
    
    window->delay = 0;
    byte* str = window->text_start + window->text_offset;

    while(true)
    {
        unsigned short y = window->window_y + window->text_y;
        while(window->text_y >= window->window_height || y > 0x1F)
        {
            properScroll(window, dest);
            y = window->window_y + window->text_y;
        }
        
        if(str[1] == 0xFF)
        {
            byte returnedVal = custom_codes_parse_generic(str[0], str, window, dest);
            if(returnedVal != 0)
            {
                window->text_offset += returnedVal;
                str += returnedVal;
            }
            else if(str[0] == 1)
            {
                window->text_y += 2;
                window->text_x = 0;
                window->pixel_x = 0;
                str += 2;
                window->text_offset += 2;
            }
            else if(str[0] == 0)
            {
                window->loaded_code = 0;
                return 0;
            }
        }
        else
        {
            if(window->text_x >= window->window_width || (window->text_x + window->window_x) > 0x1F)
            {
                window->text_y += 2;
                window->text_x = 0;
                window->pixel_x = 0;
            }
            weld_entry_custom_buffer(window, str, 0, 0xF, dest);
            str++;
            window->text_offset++;
        }
    }
}

int check_overworld_buffer()
{
    int address = *((int*)(OVERWORLD_BUFFER_POINTER));
    if(address == 0)
    {
        int tmp_counter = m2_buffer_counter;
        address = (int)m2_malloc(OVERWORLD_BUFFER_SIZE);
        *((int*)(OVERWORLD_BUFFER_POINTER)) = address;
        m2_buffer_counter = tmp_counter;
    }
    return address;
}

void free_overworld_buffer()
{
    int address = *((int*)(OVERWORLD_BUFFER_POINTER));
    if(address != 0)
    {
        int tmp_counter = m2_buffer_counter;
        m2_free((int*)address);
        *((int*)(OVERWORLD_BUFFER_POINTER)) = 0;
        m2_buffer_counter = tmp_counter;
    }
}

void load_pixels_overworld_buffer()
{
    int tile = *tile_offset;
    byte* buffer = (byte*)(OVERWORLD_BUFFER - (tile * TILESET_OFFSET_BUFFER_MULTIPLIER));
    int* topBufferValues = (int*)(&buffer[tile * 8]);
    int* bottomBufferValues = topBufferValues + 0x40;
    int* topTilePointer;
    int nextValue = 0x20;
    int i = 0;
    while(i < (0x1C * 8))
    {
        //Using pointers instead of values directly saves another 14k cycles. The total amount of cycles this routine now takes is about 92k
        tile = m2_coord_table_fast_progression[i];
        int remainingTiles = tile >> 0xB;
        tile = (tile & 0x7FF) + (*tile_offset);
        topTilePointer = &vram[(tile * 8)];
        if(i == nextValue)
        {
            nextValue += 0x20;
            topBufferValues = bottomBufferValues;
            bottomBufferValues += 0x40;
        }
        i++;
        //Using "reduce_bit_depth_sp" reduced the total amount of cycles from 300k to 162k
        reduce_bit_depth_sp(topTilePointer, topBufferValues, bottomBufferValues);
        topTilePointer += 8;
        topBufferValues += 2;
        bottomBufferValues++;
        while(remainingTiles > 0)
        {
            if(i == nextValue)
            {
                nextValue += 0x20;
                topBufferValues = bottomBufferValues;
                bottomBufferValues += 0x40;
            }
            i++;
            reduce_bit_depth_sp(topTilePointer, topBufferValues, bottomBufferValues);
            topTilePointer += 8;
            topBufferValues += 2;
            bottomBufferValues++;
            remainingTiles--;
        }
    }
}

void store_pixels_overworld_buffer(int totalYs)
{
    int tile = *tile_offset;
    byte* buffer = (byte*)(OVERWORLD_BUFFER - (tile * TILESET_OFFSET_BUFFER_MULTIPLIER));
    totalYs >>= 1;
    int total = totalYs * 0x1C;
    int* topBufferValues = (int*)(&buffer[tile * 8]);
    int* bottomBufferValues = topBufferValues + 0x40;
    int* topTilePointer;
    int* bottomTilePointer;
    int* bits_to_nybbles_pointer = m2_bits_to_nybbles_fast;
    int bits_to_nybbles_array[0x100];
    //It's convenient to copy the table in IWRAM (about 0x400 cycles) only if we have more than 0x55 total tiles to copy ((total * 0xC * 2) = total cycles used reading from EWRAM vs. (total * 0xC) + 0x400 = total cycles used writing to and reading from IWRAM)
    //From a full copy it saves about 15k cycles
    if(total >= 0x56)
    {
        cpufastset(bits_to_nybbles_pointer, bits_to_nybbles_array, 0x100);
        bits_to_nybbles_pointer = bits_to_nybbles_array;
    }
    int nextValue = 0x20;
    int i = 0;
    while(i < total)
    {
        //Not using functions for the tile values saves about 30k cycles on average
        //Using pointers + a way to keep track of subsequent tiles saves 50k cycles on average from a full copy
        //m2_coord_table_fast_progression has the tile number and the number of tiles used without interruction after it in a single short
        tile = m2_coord_table_fast_progression[i];
        int remainingTiles = tile >> 0xB;
        tile = (tile & 0x7FF) + (*tile_offset);
        topTilePointer = &vram[(tile * 8)];
        bottomTilePointer = topTilePointer + (0x20 * 8);
        if(i == nextValue)
        {
            nextValue += 0x20;
            topBufferValues += 0x20;
            bottomBufferValues += 0x40;
        }
        i++;
        unsigned int first_half = *(topBufferValues++);
        unsigned int second_half = *(topBufferValues++);
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
        first_half = *(bottomBufferValues++);
        //second_half = *(bottomBufferValues++);
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
        //Since those are unused
        bottomTilePointer += 4;
        /* The game doesn't use these
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
        */
        
        while(remainingTiles > 0)
        {
            if(i == nextValue)
            {
                nextValue += 0x20;
                topBufferValues += 0x20;
                bottomBufferValues += 0x40;
            }
            i++;
            first_half = *(topBufferValues++);
            second_half = *(topBufferValues++);
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
            first_half = *(bottomBufferValues++);
            //second_half = *(bottomBufferValues++);
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
            //Since those are unused
            bottomTilePointer += 4;
            /* The game doesn't use these
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
            */
            remainingTiles--;
        }
    }
}

void store_pixels_overworld_buffer_totalTiles(int totalTiles)
{
    int tile = *tile_offset;
    byte* buffer = (byte*)(OVERWORLD_BUFFER - (tile * TILESET_OFFSET_BUFFER_MULTIPLIER));
    int* topBufferValues = (int*)(&buffer[tile * 8]);
    int* bottomBufferValues = topBufferValues + 0x40;
    int* topTilePointer;
    int* bottomTilePointer;
    int* bits_to_nybbles_pointer = m2_bits_to_nybbles_fast;
    int bits_to_nybbles_array[0x100];
    //It's convenient to copy the table in IWRAM (about 0x400 cycles) only if we have more than 0x55 total tiles to copy ((total * 0xC * 2) = total cycles used reading from EWRAM vs. (total * 0xC) + 0x400 = total cycles used writing to and reading from IWRAM)
    //From a full copy it saves about 15k cycles
    if(totalTiles >= 0x56)
    {
        cpufastset(bits_to_nybbles_pointer, bits_to_nybbles_array, 0x100);
        bits_to_nybbles_pointer = bits_to_nybbles_array;
    }
    int nextValue = 0x20;
    int i = 0;
    while(i < totalTiles)
    {
        //Not using functions for the tile values saves about 30k cycles on average
        //Using pointers + a way to keep track of subsequent tiles saves 50k cycles on average
        //m2_coord_table_fast_progression has the tile number and the number of tiles used without interruction after it in a single short
        tile = m2_coord_table_fast_progression[i];
        int remainingTiles = tile >> 0xB;
        tile = (tile & 0x7FF) + (*tile_offset);
        topTilePointer = &vram[(tile * 8)];
        bottomTilePointer = topTilePointer + (0x20 * 8);
        if(i == nextValue)
        {
            nextValue += 0x20;
            topBufferValues += 0x20;
            bottomBufferValues += 0x40;
        }
        i++;
        unsigned int first_half = *(topBufferValues++);
        unsigned int second_half = *(topBufferValues++);
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
        *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
        first_half = *(bottomBufferValues++);
        //second_half = *(bottomBufferValues++);
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
        //Since those are unused
        bottomTilePointer += 4;
        /* The game doesn't use these
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
        *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
        */
        
        while(remainingTiles > 0 && i < totalTiles)
        {
            if(i == nextValue)
            {
                nextValue += 0x20;
                topBufferValues += 0x20;
                bottomBufferValues += 0x40;
            }
            i++;
            first_half = *(topBufferValues++);
            second_half = *(topBufferValues++);
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
            *(topTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
            first_half = *(bottomBufferValues++);
            //second_half = *(bottomBufferValues++);
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 8) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x10) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(first_half >> 0x18) & 0xFF];
            //Since those are unused
            bottomTilePointer += 4;
            /* The game doesn't use these
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 8) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x10) & 0xFF];
            *(bottomTilePointer++) = bits_to_nybbles_pointer[(second_half >> 0x18) & 0xFF];
            */
            remainingTiles--;
        }
    }
}

// x, y, width: tile coordinates
void print_blankstr_window_buffer(int x, int y, int width, WINDOW* window)
{
    print_blankstr_buffer(x + window->window_x, y + window->window_y, width, (byte*)(OVERWORLD_BUFFER - ((*tile_offset) * TILESET_OFFSET_BUFFER_MULTIPLIER)));
}

// x,y: tile coordinates
void copy_tile_buffer(int xSource, int ySource, int xDest, int yDest, byte *dest)
{
    int sourceTileIndex = get_tile_number_buffer(xSource, ySource) + *tile_offset;
    int destTileIndex = get_tile_number_buffer(xDest, yDest) + *tile_offset;
    int* sourceTile = (int*)&dest[convert_tile_address_buffer(sourceTileIndex)];
    int* destTile = (int*)&dest[convert_tile_address_buffer(destTileIndex)];
    
    //Copy the first part, no matter what
    destTile[0] = sourceTile[0];
    //Handle the 4 different cases
    if(((sourceTileIndex - *tile_offset) & 0x20) == 0)
    {
        if(((destTileIndex - *tile_offset) & 0x20) == 0)
            destTile[1] = sourceTile[1];
    }
    else
    {
        if(((destTileIndex - *tile_offset) & 0x20) == 0)
            destTile[1] = 0;
    }
    
}

// x,y: tile coordinates
void copy_tile_up_buffer(int x, int y, byte *dest)
{
    copy_tile_buffer(x, y, x, y - 2, dest);
}