static WritingBuffer renderText(Render r)
        {
            WritingBuffer a = new WritingBuffer();

            byte[] text = getTextBytes(r.Text);
            int    len  = getTextLength(text, r.Font);
            int    x    = r.Center_X - (len / 2);

            //Boundary checks
            if (x + len > 240)
            {
                x = 240 - len;
            }
            if (x < 0)
            {
                x = 0;
            }

            a.startPos = (x >> 3);
            int bufferPos = x & 7;

            for (int i = 0; i < text.Length; i++)
            {
                bufferPos += _1bppRenderChar(text[i], bufferPos, r.Font, a);
            }

            a.used = (bufferPos + 7) >> 3;
            return(a);
        }
        static byte _1bppRenderChar(byte chr, int x, int font, WritingBuffer buf)
        {
            //Renders a character
            int tileHeight   = 2;
            int tileWidth    = 2;
            int tileX        = x >> 3;
            int chrPos       = chr * tileWidth * tileHeight * 8;
            int offsetX      = x & 7;
            int startOffsetY = 3 & 7;

            if (font == 1) //Saturn font is higher, for some reason...
            {
                startOffsetY = 0;
            }
            byte vWidth = Fonts[font].fontWidth[chr * 2];
            byte rWidth = Fonts[font].fontWidth[(chr * 2) + 1];

            if (font == 1 && vWidth != rWidth) //The Saturn font is compressed horizontally by removing 1 trailing pixel
            {
                vWidth -= 1;
            }

            for (int dTileY = 0; dTileY < tileHeight; dTileY++)
            {
                int dTileX        = 0;
                int renderedWidth = rWidth;
                while (renderedWidth > 0)
                {
                    int       offsetY    = startOffsetY & 7;
                    int       tileIndexX = tileX + dTileX;
                    int       tileIndexY = dTileY;
                    _1bppTile leftTile   = buf.tiles[dTileY, tileIndexX];
                    _1bppTile rightTile  = buf.tiles[dTileY, tileIndexX + 1];

                    for (int row = 0; row < 8; row++)
                    {
                        ushort canvasRow = (ushort)(leftTile.getRow(row + offsetY) | (rightTile.getRow(row + offsetY) << 8));
                        ushort glyphRow  = (ushort)(Fonts[font].font[chrPos + row + (((dTileY * tileWidth) + dTileX) * 8)] << offsetX);

                        canvasRow |= glyphRow;
                        leftTile.setRow(row + offsetY, (byte)(canvasRow & 0xFF));
                        rightTile.setRow(row + offsetY, (byte)((canvasRow >> 8) & 0xFF));

                        if (row != 7 && row + offsetY == 7)
                        {
                            offsetY = -(row + 1);
                            tileIndexY++;
                            leftTile  = buf.tiles[tileIndexY, tileIndexX];
                            rightTile = buf.tiles[tileIndexY, tileIndexX + 1];
                        }
                    }

                    renderedWidth -= 8;
                    dTileX++;
                }
            }
            return(vWidth);
        }
 static byte[] prepareSeparateRenderData(WritingBuffer buf, Render ren)
 {
     byte[] newArr = new byte[8];                                   //Data used to display the separate arrangements
     writeUShortToByteArrLE(newArr, 0, (ushort)(buf.startPos + 1)); //The + 1 is needed because the map starts from 1, not from 0
     writeUShortToByteArrLE(newArr, 2, (ushort)(ren.Y + 0x19));     //Apparently, the actual arrangements start at y = 0x19...
     writeUShortToByteArrLE(newArr, 4, (ushort)(buf.used));
     writeUShortToByteArrLE(newArr, 6, (ushort)(WritingBuffer.yLength));
     return(newArr);
 }
        static int insertInGraphics(WritingBuffer buf, int usedInGraphics)
        {
            //Optimally inserts the graphics inside the final product
            int total = usedInGraphics;

            for (int i = 0; i < buf.used; i++)
            {
                for (int j = 0; j < WritingBuffer.yLength; j++)
                {
                    _1bppTile tile = buf.tiles[j, i];

                    int rot = 0;
                    int pos = -1;
                    if (emptyTile.Equals(tile))
                    {
                        pos = 0x2FF; //Empty tile
                    }
                    else
                    {
                        pos = getPosInFinal(tile, total, _1bppGraphics);
                    }

                    if (pos == -1)
                    {
                        rot = 1;
                        pos = getPosInFinal(tile, total, _1bppGraphics_RotX);

                        if (pos == -1)
                        {
                            rot = 2;
                            pos = getPosInFinal(tile, total, _1bppGraphics_RotY);

                            if (pos == -1)
                            {
                                rot = 3;
                                pos = getPosInFinal(tile, total, _1bppGraphics_RotXY);
                            }
                        }
                    }

                    if (pos == -1) //Hasn't been found in any of the ways the buffer can be looked at
                    {
                        rot = 0;
                        pos = total++;
                        _1bppGraphics[pos]       = tile; //If we're here, we already calculated all four of them
                        _1bppGraphics_RotX[pos]  = tile.rotateX();
                        _1bppGraphics_RotY[pos]  = tile.rotateY();
                        _1bppGraphics_RotXY[pos] = _1bppGraphics_RotX[pos].rotateY();
                    }

                    buf.arrangements[j, i] = (ushort)(Palette | (pos + arrStart) | (rot << 0xA));
                }
            }

            return(total);
        }
        static byte[] prepareSeparateRender(WritingBuffer buf)
        {
            //Converts the arrangements to an array that is to be included separately
            byte[] newArr = new byte[WritingBuffer.yLength * buf.used * 2];
            for (int j = 0; j < WritingBuffer.yLength; j++)
            {
                for (int i = 0; i < buf.used; i++)
                {
                    writeUShortToByteArrLE(newArr, (i + (j * buf.used)) * 2, buf.arrangements[j, i]);
                }
            }

            return(newArr);
        }
        static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                return;
            }

            //Initialization
            string rendersJson = File.ReadAllText(args[0]);
            string dataFolder  = args[1] + Path.DirectorySeparatorChar;

            Fonts    = new RenderFont[2];
            Fonts[0] = new RenderFont(dataFolder, "main");
            Fonts[1] = new RenderFont(dataFolder, "saturn");
            byte[]        CastGraphics     = File.ReadAllBytes(dataFolder + "cast_sign_graphics.bin");
            byte[]        CastArrangements = File.ReadAllBytes(dataFolder + "cast_sign_arrangements.bin");
            List <Render> renders          = JsonConvert.DeserializeObject <RenderRoot>(rendersJson).Renders;

            BitsToNybbleLookup = Asset.ReadAllBytes("bits_to_nybbles.bin");
            m12CharByteLookup  = JsonConvert.DeserializeObject <Dictionary <string, byte> >(Asset.ReadAllText("m12-byte-lookup.json"));
            Graphics           = new byte[0x8000];
            Arrangements       = new ushort[0x48E0];

            for (int i = 0; i < Arrangements.Length; i++)
            {
                Arrangements[i] = 0x3FF; //Empty tile
            }
            for (int i = 0; i < CastGraphics.Length; i++)
            {
                Graphics[0x8000 - CastGraphics.Length + i] = CastGraphics[i]; //Put the CAST graphics in
            }
            int castArrPos = readIntLE(CastArrangements, 0);                  //First 4 bytes are the position of the CAST arrangements

            for (int i = 0; i < ((CastArrangements.Length - 4) >> 1); i++)    //Put the CAST arrangements in
            {
                Arrangements[(castArrPos / 2) + i] = readUShortLE(CastArrangements, (i * 2) + 4);
            }

            int maxTiles = 0x300 - (CastGraphics.Length / 0x20);

            _1bppGraphics       = new _1bppTile[maxTiles];
            _1bppGraphics_RotX  = new _1bppTile[maxTiles];
            _1bppGraphics_RotY  = new _1bppTile[maxTiles];
            _1bppGraphics_RotXY = new _1bppTile[maxTiles];
            int UsedTiles = 0;

            WritingBuffer[] buffers = new WritingBuffer[renders.Count];

            //Render the text as 1bpp
            for (int i = 0; i < renders.Count; i++)
            {
                buffers[i] = renderText(renders[i]);
            }

            for (int i = 0; i < buffers.Length; i++)
            {
                UsedTiles = insertInGraphics(buffers[i], UsedTiles);
            }

            //Put the arrangements in
            for (int i = 0; i < renders.Count; i++)
            {
                int pos = buffers[i].startPos + 1 + (renders[i].Y * 0x20); //The + 1 is here because the scene's map starts from tile 1. Not tile 0
                if (renders[i].Separate_file_name == "")
                {
                    for (int j = 0; j < buffers[i].used; j++)
                    {
                        for (int k = 0; k < WritingBuffer.yLength; k++)
                        {
                            Arrangements[pos + j + (k * 0x20)] = buffers[i].arrangements[k, j];
                        }
                    }
                }
                else
                {
                    //This is an arrangement that changes when the game is running. Set the starting arrangements to empty
                    for (int j = 0; j < buffers[i].used; j++)
                    {
                        for (int k = 0; k < WritingBuffer.yLength; k++)
                        {
                            Arrangements[pos + j + (k * 0x20)] = 0x3FF; //Empty tile
                        }
                    }
                    //Save this in an external file
                    File.WriteAllBytes(dataFolder + "cast_roll_" + renders[i].Separate_file_name + "_arrangement.bin", prepareSeparateRender(buffers[i]));
                    File.WriteAllBytes(dataFolder + "cast_roll_" + renders[i].Separate_file_name + "_data.bin", prepareSeparateRenderData(buffers[i], renders[i]));
                    File.WriteAllBytes(dataFolder + "cast_roll_" + renders[i].Separate_file_name + "_size.bin", prepareSeparateRenderSize(buffers[i]));
                }
            }

            //Convert the 1bpp tiles to 4bpp
            for (int tile = 0; tile < UsedTiles; tile++)
            {
                int       basePos            = (tile * 0x20) + 0x2000;
                _1bppTile pre_converted_tile = _1bppGraphics[tile];
                for (int i = 0; i < 8; i++)
                {
                    int row = readIntLE(BitsToNybbleLookup, pre_converted_tile.getRow(i) * 4);
                    for (int j = 0; j < 4; j++)
                    {
                        Graphics[basePos + (i * 4) + j] = (byte)((row >> (j * 8)) & 0xFF);
                    }
                }
            }

            //File.WriteAllBytes(dataFolder + "cast_roll_graphics.bin", Graphics);
            //File.WriteAllBytes(dataFolder + "cast_roll_arrangements.bin", convertUShortArrToByteArrLE(Arrangements));

            File.WriteAllBytes(dataFolder + "cast_roll_first_free.bin", convertUShortToByteArrLE((ushort)(UsedTiles + 0x100)));
            File.WriteAllBytes(dataFolder + "cast_roll_graphics_[c].bin", GBA.LZ77.Compress(Graphics));
            File.WriteAllBytes(dataFolder + "cast_roll_arrangements_[c].bin", GBA.LZ77.Compress(convertUShortArrToByteArrLE(Arrangements)));
        }
 static byte[] prepareSeparateRenderSize(WritingBuffer buf)
 {
     byte[] newArr = new byte[1]; //Used in order to have a properly sized buffer
     newArr[0] = (byte)((buf.used * 2 * WritingBuffer.yLength) & 0xFF);
     return(newArr);
 }