From eeb626947eac81b9d9d9a90ad6e29035c21d54a7 Mon Sep 17 00:00:00 2001 From: Thomas Guillemard Date: Sat, 4 Aug 2018 23:38:49 +0200 Subject: [PATCH] Implement Shared Fonts (#215) * Implement Shared Fonts This fully implements shared fonts. This commit is provided without fonts. This commit also add Size to HSharedMem.Positions to be able to add fonts to shared zones when RequestLoad is called. * Require the user to provide fonts in RyuFS/system * Use File.Exits instead of relying ona try/catch and change system resource exception format a bit * Make sure that font sum doesn't exceed 17MB Also rename font data dictionary for coherence. --- Ryujinx.HLE/Font/SharedFontManager.cs | 177 ++++++++++++++++++ .../Services/Pl => Font}/SharedFontType.cs | 4 +- Ryujinx.HLE/Hid/Hid.cs | 10 +- Ryujinx.HLE/Logging/LogClass.cs | 1 + Ryujinx.HLE/OsHle/Handles/HSharedMem.cs | 14 +- Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs | 8 +- Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs | 10 +- Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs | 1 - .../OsHle/Services/Pl/ISharedFontManager.cs | 72 ++++++- .../InvalidSystemResourceException.cs | 13 ++ Ryujinx.HLE/Ryujinx.HLE.csproj | 2 +- Ryujinx.HLE/Switch.cs | 12 +- Ryujinx.HLE/VirtualFileSystem.cs | 3 + 13 files changed, 292 insertions(+), 35 deletions(-) create mode 100644 Ryujinx.HLE/Font/SharedFontManager.cs rename Ryujinx.HLE/{OsHle/Services/Pl => Font}/SharedFontType.cs (76%) create mode 100644 Ryujinx.HLE/Resource/InvalidSystemResourceException.cs diff --git a/Ryujinx.HLE/Font/SharedFontManager.cs b/Ryujinx.HLE/Font/SharedFontManager.cs new file mode 100644 index 0000000000..fce270de86 --- /dev/null +++ b/Ryujinx.HLE/Font/SharedFontManager.cs @@ -0,0 +1,177 @@ +using ChocolArm64.Exceptions; +using ChocolArm64.Memory; +using Ryujinx.HLE.Logging; +using Ryujinx.HLE.OsHle; +using Ryujinx.HLE.OsHle.Handles; +using Ryujinx.HLE.Resource; +using System; +using System.Collections.Generic; +using System.IO; + + +namespace Ryujinx.HLE.Font +{ + public class SharedFontManager + { + private const uint SharedMemorySize = 0x1100000; + private Logger Log; + + private string FontsPath; + + private object ShMemLock; + + private (AMemory, long, long)[] ShMemPositions; + + private Dictionary FontData; + + private uint[] LoadedFonts; + + public SharedFontManager(Logger Log, string SystemPath) + { + this.Log = Log; + this.FontsPath = Path.Combine(SystemPath, "fonts"); + + ShMemLock = new object(); + + ShMemPositions = new(AMemory, long, long)[0]; + + FontData = new Dictionary() + { + { SharedFontType.JapanUsEurope, GetData("FontStandard") }, + { SharedFontType.SimplifiedChinese, GetData("FontChineseSimplified") }, + { SharedFontType.SimplifiedChineseEx, GetData("FontExtendedChineseSimplified") }, + { SharedFontType.TraditionalChinese, GetData("FontChineseTraditional") }, + { SharedFontType.Korean, GetData("FontKorean") }, + { SharedFontType.NintendoEx, GetData("FontNintendoExtended") } + }; + + int FontMemoryUsage = 0; + foreach (byte[] data in FontData.Values) + { + FontMemoryUsage += data.Length; + FontMemoryUsage += 0x8; + } + + if (FontMemoryUsage > SharedMemorySize) + { + throw new InvalidSystemResourceException($"The sum of all fonts size exceed the shared memory size. Please make sure that the fonts don't exceed {SharedMemorySize} bytes in total. (actual size: {FontMemoryUsage} bytes)"); + } + + LoadedFonts = new uint[FontData.Count]; + } + + public byte[] GetData(string FontName) + { + string FontFilePath = Path.Combine(FontsPath, $"{FontName}.ttf"); + if (File.Exists(FontFilePath)) + { + return File.ReadAllBytes(FontFilePath); + } + else + { + throw new InvalidSystemResourceException($"Font \"{FontName}.ttf\" not found. Please provide it in \"{FontsPath}\"."); + } + } + + public void MapFont(SharedFontType FontType, AMemory Memory, long Position) + { + uint SharedMemoryAddressOffset = GetSharedMemoryAddressOffset(FontType); + // TODO: find what are the 8 bytes before the font + Memory.WriteUInt64(Position + SharedMemoryAddressOffset - 8, 0); + Memory.WriteBytes(Position + SharedMemoryAddressOffset, FontData[FontType]); + } + + public void PropagateNewMapFont(SharedFontType Type) + { + lock (ShMemLock) + { + foreach ((AMemory Memory, long Position, long Size) in ShMemPositions) + { + AMemoryMapInfo MemoryInfo = Memory.Manager.GetMapInfo(Position); + + if (MemoryInfo == null) + { + throw new VmmPageFaultException(Position); + } + + // The memory is read only, we need to changes that to add the new font + AMemoryPerm originalPerms = MemoryInfo.Perm; + Memory.Manager.Reprotect(Position, Size, AMemoryPerm.RW); + MapFont(Type, Memory, Position); + Memory.Manager.Reprotect(Position, Size, originalPerms); + } + } + } + + internal void ShMemMap(object sender, EventArgs e) + { + HSharedMem SharedMem = (HSharedMem)sender; + + lock (ShMemLock) + { + ShMemPositions = SharedMem.GetVirtualPositions(); + + (AMemory Memory, long Position, long Size) = ShMemPositions[ShMemPositions.Length - 1]; + + for (int Type = 0; Type < LoadedFonts.Length; Type++) + { + if (LoadedFonts[(int)Type] == 1) + { + MapFont((SharedFontType)Type, Memory, Position); + } + } + } + } + + internal void ShMemUnmap(object sender, EventArgs e) + { + HSharedMem SharedMem = (HSharedMem)sender; + + lock (ShMemLock) + { + ShMemPositions = SharedMem.GetVirtualPositions(); + } + } + + public void Load(SharedFontType FontType) + { + if (LoadedFonts[(int)FontType] == 0) + { + PropagateNewMapFont(FontType); + } + + LoadedFonts[(int)FontType] = 1; + } + + public uint GetLoadState(SharedFontType FontType) + { + if (LoadedFonts[(int)FontType] != 1) + { + // Some games don't request a load, so we need to load it here. + Load(FontType); + return 0; + } + return LoadedFonts[(int)FontType]; + } + + public uint GetFontSize(SharedFontType FontType) + { + return (uint)FontData[FontType].Length; + } + + public uint GetSharedMemoryAddressOffset(SharedFontType FontType) + { + uint Pos = 0x8; + + for (SharedFontType Type = SharedFontType.JapanUsEurope; Type < FontType; Type++) + { + Pos += GetFontSize(Type); + Pos += 0x8; + } + + return Pos; + } + + public int Count => FontData.Count; + } +} diff --git a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs b/Ryujinx.HLE/Font/SharedFontType.cs similarity index 76% rename from Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs rename to Ryujinx.HLE/Font/SharedFontType.cs index 97fd95dc9d..ca8a42b0cd 100644 --- a/Ryujinx.HLE/OsHle/Services/Pl/SharedFontType.cs +++ b/Ryujinx.HLE/Font/SharedFontType.cs @@ -1,6 +1,6 @@ -namespace Ryujinx.HLE.OsHle.Services.Pl +namespace Ryujinx.HLE.Font { - enum SharedFontType + public enum SharedFontType { JapanUsEurope = 0, SimplifiedChinese = 1, diff --git a/Ryujinx.HLE/Hid/Hid.cs b/Ryujinx.HLE/Hid/Hid.cs index 2f007f1fb0..43c040bb5d 100644 --- a/Ryujinx.HLE/Hid/Hid.cs +++ b/Ryujinx.HLE/Hid/Hid.cs @@ -67,7 +67,7 @@ namespace Ryujinx.HLE.Input private object ShMemLock; - private (AMemory, long)[] ShMemPositions; + private (AMemory, long, long)[] ShMemPositions; public Hid(Logger Log) { @@ -75,7 +75,7 @@ namespace Ryujinx.HLE.Input ShMemLock = new object(); - ShMemPositions = new (AMemory, long)[0]; + ShMemPositions = new (AMemory, long, long)[0]; } internal void ShMemMap(object sender, EventArgs e) @@ -86,7 +86,7 @@ namespace Ryujinx.HLE.Input { ShMemPositions = SharedMem.GetVirtualPositions(); - (AMemory Memory, long Position) = ShMemPositions[ShMemPositions.Length - 1]; + (AMemory Memory, long Position, long Size) = ShMemPositions[ShMemPositions.Length - 1]; for (long Offset = 0; Offset < Horizon.HidSize; Offset += 8) { @@ -167,7 +167,7 @@ namespace Ryujinx.HLE.Input { lock (ShMemLock) { - foreach ((AMemory Memory, long Position) in ShMemPositions) + foreach ((AMemory Memory, long Position, long Size) in ShMemPositions) { long ControllerOffset = Position + HidControllersOffset; @@ -218,7 +218,7 @@ namespace Ryujinx.HLE.Input { lock (ShMemLock) { - foreach ((AMemory Memory, long Position) in ShMemPositions) + foreach ((AMemory Memory, long Position, long Size) in ShMemPositions) { long TouchScreenOffset = Position + HidTouchScreenOffset; diff --git a/Ryujinx.HLE/Logging/LogClass.cs b/Ryujinx.HLE/Logging/LogClass.cs index c377ace662..95cae7e0ab 100644 --- a/Ryujinx.HLE/Logging/LogClass.cs +++ b/Ryujinx.HLE/Logging/LogClass.cs @@ -4,6 +4,7 @@ namespace Ryujinx.HLE.Logging { Audio, Cpu, + Font, Gpu, Hid, Kernel, diff --git a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs index 6426e585ea..cd3d82237c 100644 --- a/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs +++ b/Ryujinx.HLE/OsHle/Handles/HSharedMem.cs @@ -6,37 +6,37 @@ namespace Ryujinx.HLE.OsHle.Handles { class HSharedMem { - private List<(AMemory, long)> Positions; + private List<(AMemory, long, long)> Positions; public EventHandler MemoryMapped; public EventHandler MemoryUnmapped; public HSharedMem() { - Positions = new List<(AMemory, long)>(); + Positions = new List<(AMemory, long, long)>(); } - public void AddVirtualPosition(AMemory Memory, long Position) + public void AddVirtualPosition(AMemory Memory, long Position, long Size) { lock (Positions) { - Positions.Add((Memory, Position)); + Positions.Add((Memory, Position, Size)); MemoryMapped?.Invoke(this, EventArgs.Empty); } } - public void RemoveVirtualPosition(AMemory Memory, long Position) + public void RemoveVirtualPosition(AMemory Memory, long Position, long Size) { lock (Positions) { - Positions.Remove((Memory, Position)); + Positions.Remove((Memory, Position, Size)); MemoryUnmapped?.Invoke(this, EventArgs.Empty); } } - public (AMemory, long)[] GetVirtualPositions() + public (AMemory, long, long)[] GetVirtualPositions() { return Positions.ToArray(); } diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs index e816c44ec3..6f7bc42fd7 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs @@ -22,7 +22,7 @@ namespace Ryujinx.HLE.OsHle.Kernel private ConcurrentDictionary SyncWaits; - private HashSet<(HSharedMem, long)> MappedSharedMems; + private HashSet<(HSharedMem, long, long)> MappedSharedMems; private ulong CurrentHeapSize; @@ -83,7 +83,7 @@ namespace Ryujinx.HLE.OsHle.Kernel SyncWaits = new ConcurrentDictionary(); - MappedSharedMems = new HashSet<(HSharedMem, long)>(); + MappedSharedMems = new HashSet<(HSharedMem, long, long)>(); } static SvcHandler() @@ -138,9 +138,9 @@ namespace Ryujinx.HLE.OsHle.Kernel { lock (MappedSharedMems) { - foreach ((HSharedMem SharedMem, long Position) in MappedSharedMems) + foreach ((HSharedMem SharedMem, long Position, long Size) in MappedSharedMems) { - SharedMem.RemoveVirtualPosition(Memory, Position); + SharedMem.RemoveVirtualPosition(Memory, Position, Size); } MappedSharedMems.Clear(); diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs index bb73f1ea17..f10cad7a20 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcMemory.cs @@ -174,15 +174,15 @@ namespace Ryujinx.HLE.OsHle.Kernel AMemoryHelper.FillWithZeros(Memory, Src, (int)Size); + SharedMem.AddVirtualPosition(Memory, Src, Size); + Memory.Manager.Reprotect(Src, Size, (AMemoryPerm)Perm); lock (MappedSharedMems) { - MappedSharedMems.Add((SharedMem, Src)); + MappedSharedMems.Add((SharedMem, Src, Size)); } - SharedMem.AddVirtualPosition(Memory, Src); - ThreadState.X0 = 0; } @@ -210,11 +210,11 @@ namespace Ryujinx.HLE.OsHle.Kernel { Memory.Manager.Unmap(Src, Size, (int)MemoryType.SharedMemory); - SharedMem.RemoveVirtualPosition(Memory, Src); + SharedMem.RemoveVirtualPosition(Memory, Src, Size); lock (MappedSharedMems) { - MappedSharedMems.Remove((SharedMem, Src)); + MappedSharedMems.Remove((SharedMem, Src, Size)); } ThreadState.X0 = 0; diff --git a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs index 08305522f5..a968a1dbc1 100644 --- a/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs +++ b/Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs @@ -242,7 +242,6 @@ namespace Ryujinx.HLE.OsHle.Kernel Process.Scheduler.Suspend(CurrThread); IpcMessage Cmd = new IpcMessage(CmdData, CmdPtr); - long Result = IpcHandler.IpcCall(Ns, Process, Memory, Session, Cmd, CmdPtr); Thread.Yield(); diff --git a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs index 9f85f3d102..b8447ac65c 100644 --- a/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs +++ b/Ryujinx.HLE/OsHle/Services/Pl/ISharedFontManager.cs @@ -1,3 +1,4 @@ +using Ryujinx.HLE.Font; using Ryujinx.HLE.OsHle.Ipc; using System.Collections.Generic; @@ -13,11 +14,12 @@ namespace Ryujinx.HLE.OsHle.Services.Pl { m_Commands = new Dictionary() { - { 0, RequestLoad }, - { 1, GetLoadState }, - { 2, GetFontSize }, - { 3, GetSharedMemoryAddressOffset }, - { 4, GetSharedMemoryNativeHandle } + { 0, RequestLoad }, + { 1, GetLoadState }, + { 2, GetFontSize }, + { 3, GetSharedMemoryAddressOffset }, + { 4, GetSharedMemoryNativeHandle }, + { 5, GetSharedFontInOrderOfPriority } }; } @@ -25,26 +27,34 @@ namespace Ryujinx.HLE.OsHle.Services.Pl { SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + Context.Ns.Font.Load(FontType); + return 0; } public long GetLoadState(ServiceCtx Context) { - Context.ResponseData.Write(1); //Loaded + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(Context.Ns.Font.GetLoadState(FontType)); return 0; } public long GetFontSize(ServiceCtx Context) { - Context.ResponseData.Write(Horizon.FontSize); + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(Context.Ns.Font.GetFontSize(FontType)); return 0; } public long GetSharedMemoryAddressOffset(ServiceCtx Context) { - Context.ResponseData.Write(0); + SharedFontType FontType = (SharedFontType)Context.RequestData.ReadInt32(); + + Context.ResponseData.Write(Context.Ns.Font.GetSharedMemoryAddressOffset(FontType)); return 0; } @@ -57,5 +67,51 @@ namespace Ryujinx.HLE.OsHle.Services.Pl return 0; } + + private uint AddFontToOrderOfPriorityList(ServiceCtx Context, SharedFontType FontType, uint BufferPos, out uint LoadState) + { + long TypesPosition = Context.Request.ReceiveBuff[0].Position; + long TypesSize = Context.Request.ReceiveBuff[0].Size; + + long OffsetsPosition = Context.Request.ReceiveBuff[1].Position; + long OffsetsSize = Context.Request.ReceiveBuff[1].Size; + + long FontSizeBufferPosition = Context.Request.ReceiveBuff[2].Position; + long FontSizeBufferSize = Context.Request.ReceiveBuff[2].Size; + + LoadState = Context.Ns.Font.GetLoadState(FontType); + + if (BufferPos >= TypesSize || BufferPos >= OffsetsSize || BufferPos >= FontSizeBufferSize) + { + return 0; + } + + Context.Memory.WriteUInt32(TypesPosition + BufferPos, (uint)FontType); + Context.Memory.WriteUInt32(OffsetsPosition + BufferPos, Context.Ns.Font.GetSharedMemoryAddressOffset(FontType)); + Context.Memory.WriteUInt32(FontSizeBufferPosition + BufferPos, Context.Ns.Font.GetFontSize(FontType)); + + BufferPos += 4; + + return BufferPos; + } + + public long GetSharedFontInOrderOfPriority(ServiceCtx Context) + { + ulong LanguageCode = Context.RequestData.ReadUInt64(); + uint LoadedCount = 0; + uint BufferPos = 0; + uint Loaded = 0; + + for (int Type = 0; Type < Context.Ns.Font.Count; Type++) + { + BufferPos = AddFontToOrderOfPriorityList(Context, (SharedFontType)Type, BufferPos, out Loaded); + LoadedCount += Loaded; + } + + Context.ResponseData.Write(LoadedCount); + Context.ResponseData.Write(Context.Ns.Font.Count); + + return 0; + } } } \ No newline at end of file diff --git a/Ryujinx.HLE/Resource/InvalidSystemResourceException.cs b/Ryujinx.HLE/Resource/InvalidSystemResourceException.cs new file mode 100644 index 0000000000..35c4874a34 --- /dev/null +++ b/Ryujinx.HLE/Resource/InvalidSystemResourceException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Ryujinx.HLE.Resource +{ + public class InvalidSystemResourceException : Exception + { + public InvalidSystemResourceException(string message) + : base(message) + { + } + + } +} diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index acef4be9e7..f7fb84a588 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 74c0564a9f..a80ca86c19 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,5 +1,6 @@ using Ryujinx.Audio; using Ryujinx.Graphics.Gal; +using Ryujinx.HLE.Font; using Ryujinx.HLE.Gpu; using Ryujinx.HLE.Input; using Ryujinx.HLE.Logging; @@ -27,6 +28,8 @@ namespace Ryujinx.HLE public Hid Hid { get; private set; } + public SharedFontManager Font { get; private set; } + public event EventHandler Finish; public Switch(IGalRenderer Renderer, IAalOutput AudioOut) @@ -57,8 +60,13 @@ namespace Ryujinx.HLE Hid = new Hid(Log); - Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; - Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; + Font = new SharedFontManager(Log, VFs.GetSystemPath()); + + Os.HidSharedMem.MemoryMapped += Hid.ShMemMap; + Os.HidSharedMem.MemoryUnmapped += Hid.ShMemUnmap; + + Os.FontSharedMem.MemoryMapped += Font.ShMemMap; + Os.FontSharedMem.MemoryUnmapped += Font.ShMemUnmap; } public void LoadCart(string ExeFsDir, string RomFsFile = null) diff --git a/Ryujinx.HLE/VirtualFileSystem.cs b/Ryujinx.HLE/VirtualFileSystem.cs index df1fc9db13..31b8e184ce 100644 --- a/Ryujinx.HLE/VirtualFileSystem.cs +++ b/Ryujinx.HLE/VirtualFileSystem.cs @@ -8,6 +8,7 @@ namespace Ryujinx.HLE private const string BasePath = "RyuFs"; private const string NandPath = "nand"; private const string SdCardPath = "sdmc"; + private const string SystemPath = "system"; public Stream RomFs { get; private set; } @@ -45,6 +46,8 @@ namespace Ryujinx.HLE public string GetGameSavesPath() => MakeDirAndGetFullPath(NandPath); + public string GetSystemPath() => MakeDirAndGetFullPath(SystemPath); + public string SwitchPathToSystemPath(string SwitchPath) { string[] Parts = SwitchPath.Split(":");