Exemple #1
0
        public static (DuplicatableStream metrics, DuplicatableStream texture, Dictionary <char, (int w1, int w2)> charToWidthMap) Run(FileFetcher _fc, Config config)
        {
            bool debug         = config.DebugFontOutputPath != null;
            bool adjustMetrics = debug;

            DuplicatableStream metricsWiiStream = _fc.GetFile("rootR.cpk/sys/FontBinary2.bin", Version.W);
            DuplicatableStream textureWiiStream = _fc.GetFile("rootR.cpk/sys/FontTexture2.tex", Version.W);
            DuplicatableStream texturePs3Stream = _fc.GetFile("rootR.cpk/sys/FontTexture2.tex", Version.U);
            FPS4 metricsWiiFps4 = new FPS4(metricsWiiStream);
            DuplicatableStream metricsWiiData = metricsWiiFps4.GetChildByIndex(1).AsFile.DataStream;
            FPS4   textureWiiFps4             = new FPS4(textureWiiStream);
            FPS4   texturePs3Fps4             = new FPS4(texturePs3Stream);
            TXM    textureWiiTxm = new TXM(textureWiiFps4.GetChildByIndex(0).AsFile.DataStream);
            TXV    textureWiiTxv = new TXV(textureWiiTxm, textureWiiFps4.GetChildByIndex(1).AsFile.DataStream, false);
            TXM    texturePs3Txm = new TXM(texturePs3Fps4.GetChildByIndex(0).AsFile.DataStream);
            TXV    texturePs3Txv = new TXV(texturePs3Txm, texturePs3Fps4.GetChildByIndex(1).AsFile.DataStream, false);
            Bitmap bitmapWii     = textureWiiTxv.textures[0].GetBitmaps()[0];
            Bitmap bitmapPs3     = texturePs3Txv.textures[0].GetBitmaps()[0];

            if (debug)
            {
                Directory.CreateDirectory(config.DebugFontOutputPath);
                bitmapWii.Save(Path.Combine(config.DebugFontOutputPath, "wii.png"));
                bitmapPs3.Save(Path.Combine(config.DebugFontOutputPath, "ps3.png"));
            }

            var       img_wii = bitmapWii;
            var       img_ps3 = bitmapPs3;
            const int tile_extent_in_image = 25;
            const int tile_extent_actual   = 24;
            int       tiles_x = (img_wii.Width + 1) / tile_extent_in_image;
            int       tiles_y = (img_wii.Height + 1) / tile_extent_in_image;
            const int ps3_tile_extent_in_image = 37;
            const int ps3_tile_extent_actual   = 36;
            int       ps3_tiles_x = (img_ps3.Width + 1) / ps3_tile_extent_in_image;
            int       ps3_tiles_y = (img_ps3.Height + 1) / ps3_tile_extent_in_image;

            // split into individual tiles and extract source colors
            HashSet <Color> colors    = new HashSet <Color>();
            List <Bitmap>   tiles_wii = new List <Bitmap>();
            List <Bitmap>   tiles_ps3 = new List <Bitmap>();

            for (int ty = 0; ty < tiles_y; ++ty)
            {
                for (int tx = 0; tx < tiles_x; ++tx)
                {
                    var bmp = new Bitmap(tile_extent_actual, tile_extent_actual);
                    for (int y = 0; y < tile_extent_actual; ++y)
                    {
                        for (int x = 0; x < tile_extent_actual; ++x)
                        {
                            var px = img_wii.GetPixel(tx * tile_extent_in_image + x, ty * tile_extent_in_image + y);
                            colors.Add(px);
                            bmp.SetPixel(x, y, px);
                        }
                    }
                    tiles_wii.Add(bmp);
                }
            }
            for (int ty = 0; ty < ps3_tiles_y; ++ty)
            {
                for (int tx = 0; tx < ps3_tiles_x; ++tx)
                {
                    var bmp = new Bitmap(ps3_tile_extent_actual, ps3_tile_extent_actual);
                    for (int y = 0; y < ps3_tile_extent_actual; ++y)
                    {
                        for (int x = 0; x < ps3_tile_extent_actual; ++x)
                        {
                            var px = img_ps3.GetPixel(tx * ps3_tile_extent_in_image + x, ty * ps3_tile_extent_in_image + y);
                            bmp.SetPixel(x, y, px);
                        }
                    }
                    tiles_ps3.Add(bmp);
                }
            }

            // inject ps3 tiles over wii tiles
            List <(int where, int ps3where, string chars)> charsets = new List <(int where, int ps3where, string chars)>();

            charsets.Add((0, 0, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
            charsets.Add((243, 74, ",."));
            charsets.Add((246, 77, ":;?!_"));
            charsets.Add((254, 83, "/\\~|…"));
            charsets.Add((260, 118, "‘"));
            charsets.Add((261, 118, "’"));
            charsets.Add((262, 119, "“"));
            charsets.Add((263, 119, "”"));
            charsets.Add((264, 93, "()[]{}"));
            charsets.Add((276, 105, "+"));
            charsets.Add((277, 82, "-"));             // copy the dash minus instead of the math minus, looks better in text flow
            charsets.Add((278, 107, "±×÷=≠<>≤≥"));
            charsets.Add((289, 118, "'\""));
            charsets.Add((293, 122, "$%#&*@"));

            Dictionary <char, (int w1, int w2)> charToWidthMap = new Dictionary <char, (int w1, int w2)>();

            byte[] metrics = new byte[metricsWiiData.Length];
            metricsWiiData.Read(metrics, 0, metrics.Length);

            foreach (var charset in charsets)
            {
                int where = charset.where;
                int ps3where = charset.ps3where;
                foreach (char ch in charset.chars)
                {
                    var wiitile       = tiles_wii[where];
                    var averagescaled = DownscaleTileFromPs3ToWiiWithUnweightedAverageScaling(tiles_ps3[ps3where]);
                    //var downscaled = DownscaleTileFromPs3ToWiiWithWeightedScaling(tiles_ps3[ps3where]);
                    var downscaled = averagescaled;
                    PosterizeImage(wiitile, downscaled, colors, tile_extent_actual);
                    PosterizeImage(averagescaled, averagescaled, colors, tile_extent_actual);
                    if (debug)
                    {
                        wiitile.Save(Path.Combine(config.DebugFontOutputPath, string.Format("wii_new_{0:D4}.png", where)));
                    }

                    int cutoff_1    = 180;
                    int cutoff_2    = 220;
                    int leftwhere   = where * 8 + 0;
                    int rightwhere  = where * 8 + 1;
                    int leftwhere2  = where * 8 + 4;
                    int rightwhere2 = where * 8 + 5;

                    // forcing vertical extents to be the same for all, because text looks *really weird* in english if lines have different heights
                    // for digits, forcing horizontal extents to be the same as well so they look nice in vertical lists
                    bool isDigit = ch == '0' || ch == '1' || ch == '2' || ch == '3' || ch == '4' || ch == '5' || ch == '6' || ch == '7' || ch == '8' || ch == '9';
                    if (isDigit)
                    {
                        metrics[leftwhere]  = ch == '1' ? (byte)(6) : ch == '2' ? (byte)(6) : (byte)(7);
                        metrics[rightwhere] = ch == '1' ? (byte)(8) : ch == '2' ? (byte)(8) : (byte)(7);
                        //metrics[leftwhere2] = ch == '1' ? (byte)(7) : ch == '2' ? (byte)(7) : (byte)(8);
                        //metrics[rightwhere2] = ch == '1' ? (byte)(9) : ch == '2' ? (byte)(9) : (byte)(8);
                    }
                    else
                    {
                        metrics[leftwhere]  = (byte)MeasureAlphaFromLeft(averagescaled, cutoff_1);
                        metrics[rightwhere] = (byte)MeasureAlphaFromRight(averagescaled, cutoff_1);
                        //metrics[leftwhere2] = (byte)MeasureAlphaFromLeft(averagescaled, cutoff_2);
                        //metrics[rightwhere2] = (byte)MeasureAlphaFromRight(averagescaled, cutoff_2);
                    }

                    switch (ch)
                    {
                    case 'j':
                    case ';':
                        metrics[leftwhere] += 1;
                        break;

                    case 'A':
                    case 'P':
                    case 'Q':
                    case 'T':
                    case 'Y':
                    case 'W':
                    case 'f':
                    case 's':
                    case 'w':
                    case 'y':
                    case '[':
                        metrics[rightwhere] += 1;
                        break;

                    case 't':
                        metrics[leftwhere]  += 1;
                        metrics[rightwhere] += 1;
                        break;

                    default:
                        break;
                    }

                    metrics[leftwhere2]  = metrics[leftwhere];
                    metrics[rightwhere2] = metrics[rightwhere];

                    switch (ch)
                    {
                    case '.':
                    case ',':
                        metrics[leftwhere2] += 1;
                        metrics[rightwhere] += 1;
                        break;

                    default:
                        break;
                    }

                    metrics[where * 8 + 2] = 0;                     //(byte)MeasureAlphaFromTop(test, cutoff_1);
                    metrics[where * 8 + 3] = 4;                     //(byte)MeasureAlphaFromBottom(test, cutoff_1);
                    metrics[where * 8 + 6] = 0;                     //(byte)MeasureAlphaFromTop(test, cutoff_2);
                    metrics[where * 8 + 7] = 4;                     //(byte)MeasureAlphaFromBottom(test, cutoff_2);

                    int width1 = tile_extent_actual - (metrics[leftwhere] + metrics[rightwhere]);
                    int width2 = tile_extent_actual - (metrics[leftwhere2] + metrics[rightwhere2]);
                    charToWidthMap.Add(ch, (width1, width2));

                    ++where;
                    ++ps3where;
                }
            }

            // manually generate good metrics for space; see also code patches in main.dol
            metrics[0x6F70] = 10;
            metrics[0x6F71] = 10;
            metrics[0x6F72] = 0;
            metrics[0x6F73] = 4;
            metrics[0x6F74] = 10;
            metrics[0x6F75] = 10;
            metrics[0x6F76] = 0;
            metrics[0x6F77] = 4;
            charToWidthMap.Add(' ', (tile_extent_actual - 20, tile_extent_actual - 20));

            // write out visual representation of font metrics for adjustments
            if (adjustMetrics)
            {
                foreach (var charset in charsets)
                {
                    int where = charset.where;
                    foreach (char ch in charset.chars)
                    {
                        int factor = 10;
                        var test   = PointScale(tiles_wii[where], factor);

                        for (int metricsset = 0; metricsset < 2; ++metricsset)
                        {
                            Color col = metricsset == 0 ? Color.Red : Color.Yellow;
                            int   xl  = (metrics[where * 8 + metricsset * 4] - 1) * factor + factor - 1;
                            int   xr  = (tiles_wii[where].Width - metrics[where * 8 + metricsset * 4 + 1]) * factor;
                            int   yt  = (metrics[where * 8 + metricsset * 4 + 2] - 1) * factor + factor - 1;
                            int   yb  = (tiles_wii[where].Width - metrics[where * 8 + metricsset * 4 + 3]) * factor;
                            for (int y = 0; y < test.Height; ++y)
                            {
                                if (xl >= 0 && xl < test.Width)
                                {
                                    test.SetPixel(xl, y, col);
                                }
                                if (xr >= 0 && xr < test.Width)
                                {
                                    test.SetPixel(xr, y, col);
                                }
                            }
                            for (int x = 0; x < test.Width; ++x)
                            {
                                if (yt >= 0 && yt < test.Height)
                                {
                                    test.SetPixel(x, yt, col);
                                }
                                if (yb >= 0 && yb < test.Height)
                                {
                                    test.SetPixel(x, yb, col);
                                }
                            }
                        }

                        PointScale(test, 3).Save(Path.Combine(config.DebugFontOutputPath, string.Format("metrics_view_{0:D4}.png", where)));

                        ++where;
                    }
                }
            }

            // join indvidiual tiles back into full texture
            int idx = 0;

            for (int ty = 0; ty < tiles_y; ++ty)
            {
                for (int tx = 0; tx < tiles_x; ++tx)
                {
                    var bmp = tiles_wii[idx];
                    for (int y = 0; y < tile_extent_actual; ++y)
                    {
                        for (int x = 0; x < tile_extent_actual; ++x)
                        {
                            var px = bmp.GetPixel(x, y);
                            img_wii.SetPixel(tx * tile_extent_in_image + x, ty * tile_extent_in_image + y, px);
                        }
                    }
                    ++idx;
                }
            }

            if (debug)
            {
                img_wii.Save(Path.Combine(config.DebugFontOutputPath, "wii_new.png"));
            }

            // inject metrics
            DuplicatableStream outputMetricsStream;
            {
                Stream stream = metricsWiiStream.Duplicate().CopyToMemory();
                stream.Position = 0x43E0;
                stream.Write(metrics);

                stream.Position = 0;
                byte[] data = new byte[stream.Length];
                stream.Read(data, 0, data.Length);
                outputMetricsStream = new HyoutaUtils.Streams.DuplicatableByteArrayStream(data);
            }

            // encode texture
            DuplicatableStream outputTextureStream;

            {
                Stream stream = textureWiiStream.Duplicate().CopyToMemory();
                stream.Position = 0x80100;
                List <(int idx, ushort v)> stuff = new List <(int idx, ushort v)>();
                for (int i = 0; i < 16; ++i)
                {
                    stuff.Add((i, stream.ReadUInt16().FromEndian(EndianUtils.Endianness.BigEndian)));
                }

                stream.Position = 0x100;
                var  pxit    = new HyoutaTools.Textures.PixelOrderIterators.TiledPixelOrderIterator(img_wii.Width, img_wii.Height, 8, 8);
                byte storage = 0;
                bool even    = false;
                foreach (var px in pxit)
                {
                    if (px.X < img_wii.Width && px.Y < img_wii.Height)
                    {
                        Color  col    = img_wii.GetPixel(px.X, px.Y);
                        ushort value  = HyoutaTools.Textures.ColorFetchingIterators.ColorFetcherGrey8Alpha8.ColorToGrey8Alpha8(col);
                        var    colidx = stuff.First(x => x.v == value).idx;
                        if (!even)
                        {
                            storage = (byte)colidx;
                        }
                        else
                        {
                            storage = (byte)(storage << 4 | (byte)colidx);
                            stream.WriteByte(storage);
                        }
                        even = !even;
                    }
                }

                stream.Position = 0;
                byte[] data = new byte[stream.Length];
                stream.Read(data, 0, data.Length);
                outputTextureStream = new HyoutaUtils.Streams.DuplicatableByteArrayStream(data);
            }

            return(outputMetricsStream, outputTextureStream, charToWidthMap);
        }
Exemple #2
0
        public static List <MemChunk> FindFreeMemoryInFontTexture(MemoryStream fontStream)
        {
            DuplicatableStream textureWiiStream = new DuplicatableByteArrayStream(fontStream.CopyToByteArray());

            HyoutaTools.Tales.Vesperia.FPS4.FPS4   textureWiiFps4 = new HyoutaTools.Tales.Vesperia.FPS4.FPS4(textureWiiStream);
            HyoutaTools.Tales.Vesperia.Texture.TXM textureWiiTxm  = new HyoutaTools.Tales.Vesperia.Texture.TXM(textureWiiFps4.GetChildByIndex(0).AsFile.DataStream);
            HyoutaTools.Tales.Vesperia.Texture.TXV textureWiiTxv  = new HyoutaTools.Tales.Vesperia.Texture.TXV(textureWiiTxm, textureWiiFps4.GetChildByIndex(1).AsFile.DataStream, false);
            Bitmap bitmapWii = textureWiiTxv.textures[2].GetBitmaps()[0];
            {
                for (int y = 0; y < bitmapWii.Height; ++y)
                {
                    for (int x = 0; x < bitmapWii.Width; ++x)
                    {
                        Color color;
                        switch (IdentifyPixel(x, y))
                        {
                        case TileIdentification.UsedTile:
                            color = Color.FromArgb(0, 255, 0);
                            break;

                        case TileIdentification.UnusedTile:
                            color = Color.FromArgb(0, 0, 255);
                            break;

                        default:
                            throw new Exception("???");
                        }
                        bitmapWii.SetPixel(x, y, color);
                    }
                }
            }

            var          pxit    = new HyoutaTools.Textures.PixelOrderIterators.TiledPixelOrderIterator(bitmapWii.Width, bitmapWii.Height, 8, 8);
            MemoryStream stream  = new MemoryStream();
            byte         storage = 0;
            bool         even    = false;

            foreach (var px in pxit)
            {
                if (px.X < bitmapWii.Width && px.Y < bitmapWii.Height)
                {
                    Color col         = bitmapWii.GetPixel(px.X, px.Y);
                    bool  pixelUnused = col.B > 0;
                    var   colidx      = pixelUnused ? 0xF : 0x0;
                    if (!even)
                    {
                        storage = (byte)colidx;
                    }
                    else
                    {
                        storage = (byte)(storage << 4 | (byte)colidx);
                        stream.WriteByte(storage == 0xFF ? (byte)1 : (byte)0);
                    }
                    even = !even;
                }
            }

            List <MemChunk> chunks = new List <MemChunk>();
            uint            offset = textureWiiFps4.Files[1].Location.Value + textureWiiTxv.textures[2].TXM.TxvLocation;
            long            len    = stream.Length;
            long            startOfLastSafeBlock = -1;
            var             fontMapper           = new FontMapper(0xE0000000 - textureWiiFps4.Files[1].Location.Value);

            stream.Position = 0;
            for (long i = 0; i <= len; ++i)
            {
                bool safeToWriteTo = i == len ? false : stream.ReadUInt8() == 1;
                if (safeToWriteTo && startOfLastSafeBlock == -1)
                {
                    // start of block
                    startOfLastSafeBlock = i;
                }
                else if (!safeToWriteTo && startOfLastSafeBlock != -1)
                {
                    // end of block
                    long     start  = startOfLastSafeBlock;
                    long     length = i - startOfLastSafeBlock;
                    MemChunk mc     = new MemChunk();
                    mc.Address    = (uint)(start + offset);
                    mc.FreeBytes  = (uint)length;
                    mc.File       = fontStream;
                    mc.Mapper     = fontMapper;
                    mc.IsInternal = false;
                    chunks.Add(mc);
                    startOfLastSafeBlock = -1;
                }
            }

            return(chunks);
        }