using LibHac; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem.Content; using Ryujinx.HLE.Resource; using Ryujinx.HLE.Utilities; using System.Collections.Generic; using System.IO; using System.Linq; using static Ryujinx.HLE.Utilities.FontUtils; namespace Ryujinx.HLE.HOS.Font { class SharedFontManager { private Switch _device; private long _physicalAddress; private string _fontsPath; private struct FontInfo { public int Offset; public int Size; public FontInfo(int offset, int size) { Offset = offset; Size = size; } } private Dictionary _fontData; public SharedFontManager(Switch device, long physicalAddress) { _physicalAddress = physicalAddress; _device = device; _fontsPath = Path.Combine(device.FileSystem.GetSystemPath(), "fonts"); } public void EnsureInitialized(ContentManager contentManager) { if (_fontData == null) { _device.Memory.FillWithZeros(_physicalAddress, Horizon.FontSize); uint fontOffset = 0; FontInfo CreateFont(string name) { if (contentManager.TryGetFontTitle(name, out long fontTitle)) { string contentPath = contentManager.GetInstalledContentPath(fontTitle, StorageId.NandSystem, ContentType.Data); string fontPath = _device.FileSystem.SwitchPathToSystemPath(contentPath); if (!string.IsNullOrWhiteSpace(fontPath)) { int fileIndex = 0; //Use second file in Chinese Font title for standard if(name == "FontChineseSimplified") { fileIndex = 1; } FileStream ncaFileStream = new FileStream(fontPath, FileMode.Open, FileAccess.Read); Nca nca = new Nca(_device.System.KeySet, ncaFileStream, false); NcaSection romfsSection = nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs); Romfs romfs = new Romfs(nca.OpenSection(romfsSection.SectionNum, false, _device.System.FsIntegrityCheckLevel)); Stream fontFile = romfs.OpenFile(romfs.Files[fileIndex]); byte[] data = DecryptFont(fontFile); FontInfo info = new FontInfo((int)fontOffset, data.Length); WriteMagicAndSize(_physicalAddress + fontOffset, data.Length); fontOffset += 8; uint start = fontOffset; for (; fontOffset - start < data.Length; fontOffset++) { _device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]); } ncaFileStream.Dispose(); nca.Dispose(); return info; } } string fontFilePath = Path.Combine(_fontsPath, name + ".ttf"); if (File.Exists(fontFilePath)) { byte[] data = File.ReadAllBytes(fontFilePath); FontInfo info = new FontInfo((int)fontOffset, data.Length); WriteMagicAndSize(_physicalAddress + fontOffset, data.Length); fontOffset += 8; uint start = fontOffset; for (; fontOffset - start < data.Length; fontOffset++) { _device.Memory.WriteByte(_physicalAddress + fontOffset, data[fontOffset - start]); } return info; } else { throw new InvalidSystemResourceException($"Font \"{name}.ttf\" not found. Please provide it in \"{_fontsPath}\"."); } } _fontData = new Dictionary() { { SharedFontType.JapanUsEurope, CreateFont("FontStandard") }, { SharedFontType.SimplifiedChinese, CreateFont("FontChineseSimplified") }, { SharedFontType.SimplifiedChineseEx, CreateFont("FontExtendedChineseSimplified") }, { SharedFontType.TraditionalChinese, CreateFont("FontChineseTraditional") }, { SharedFontType.Korean, CreateFont("FontKorean") }, { SharedFontType.NintendoEx, CreateFont("FontNintendoExtended") } }; if (fontOffset > Horizon.FontSize) { throw new InvalidSystemResourceException( $"The sum of all fonts size exceed the shared memory size. " + $"Please make sure that the fonts don't exceed {Horizon.FontSize} bytes in total. " + $"(actual size: {fontOffset} bytes)."); } } } private void WriteMagicAndSize(long position, int size) { const int decMagic = 0x18029a7f; const int key = 0x49621806; int encryptedSize = EndianSwap.Swap32(size ^ key); _device.Memory.WriteInt32(position + 0, decMagic); _device.Memory.WriteInt32(position + 4, encryptedSize); } public int GetFontSize(SharedFontType fontType) { EnsureInitialized(_device.System.ContentManager); return _fontData[fontType].Size; } public int GetSharedMemoryAddressOffset(SharedFontType fontType) { EnsureInitialized(_device.System.ContentManager); return _fontData[fontType].Offset + 8; } } }