diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index c7a824c03f..f8ec89140e 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -47,6 +47,10 @@ namespace Ryujinx.HLE.HOS private bool HasStarted; + public Nacp ControlData { get; set; } + + public string CurrentTitle { get; private set; } + public Horizon(Switch Device) { this.Device = Device; @@ -137,6 +141,8 @@ namespace Ryujinx.HLE.HOS throw new NotImplementedException("32-bit titles are unsupported!"); } + CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16"); + LoadNso("rtld"); MainProcess.SetEmptyArgs(); @@ -154,27 +160,28 @@ namespace Ryujinx.HLE.HOS Xci Xci = new Xci(KeySet, File); - Nca Nca = GetXciMainNca(Xci); + (Nca MainNca, Nca ControlNca) = GetXciGameData(Xci); - if (Nca == null) + if (MainNca == null) { Device.Log.PrintError(LogClass.Loader, "Unable to load XCI"); return; } - LoadNca(Nca); + LoadNca(MainNca, ControlNca); } - private Nca GetXciMainNca(Xci Xci) + private (Nca Main, Nca Control) GetXciGameData(Xci Xci) { if (Xci.SecurePartition == null) { throw new InvalidDataException("Could not find XCI secure partition"); } - Nca MainNca = null; - Nca PatchNca = null; + Nca MainNca = null; + Nca PatchNca = null; + Nca ControlNca = null; foreach (PfsFileEntry FileEntry in Xci.SecurePartition.Files.Where(x => x.Name.EndsWith(".nca"))) { @@ -193,6 +200,10 @@ namespace Ryujinx.HLE.HOS PatchNca = Nca; } } + else if (Nca.Header.ContentType == ContentType.Control) + { + ControlNca = Nca; + } } if (MainNca == null) @@ -201,8 +212,24 @@ namespace Ryujinx.HLE.HOS } MainNca.SetBaseNca(PatchNca); + + if (ControlNca != null) + { + ReadControlData(ControlNca); + } - return MainNca; + return (MainNca, ControlNca); + } + + public void ReadControlData(Nca ControlNca) + { + Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false)); + + byte[] ControlFile = ControlRomfs.GetFile("/control.nacp"); + + BinaryReader Reader = new BinaryReader(new MemoryStream(ControlFile)); + + ControlData = new Nacp(Reader); } public void LoadNca(string NcaFile) @@ -211,7 +238,7 @@ namespace Ryujinx.HLE.HOS Nca Nca = new Nca(KeySet, File, true); - LoadNca(Nca); + LoadNca(Nca, null); } public void LoadNsp(string NspFile) @@ -231,25 +258,37 @@ namespace Ryujinx.HLE.HOS KeySet.TitleKeys[Ticket.RightsId] = Ticket.GetTitleKey(KeySet); } + Nca MainNca = null; + Nca ControlNca = null; + foreach (PfsFileEntry NcaFile in Nsp.Files.Where(x => x.Name.EndsWith(".nca"))) { Nca Nca = new Nca(KeySet, Nsp.OpenFile(NcaFile), true); if (Nca.Header.ContentType == ContentType.Program) { - LoadNca(Nca); - - return; + MainNca = Nca; } + else if (Nca.Header.ContentType == ContentType.Control) + { + ControlNca = Nca; + } + } + + if (MainNca != null) + { + LoadNca(MainNca, ControlNca); + + return; } Device.Log.PrintError(LogClass.Loader, "Could not find an Application NCA in the provided NSP file"); } - public void LoadNca(Nca Nca) + public void LoadNca(Nca MainNca, Nca ControlNca) { - NcaSection RomfsSection = Nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs); - NcaSection ExefsSection = Nca.Sections.FirstOrDefault(x => x?.IsExefs == true); + NcaSection RomfsSection = MainNca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs); + NcaSection ExefsSection = MainNca.Sections.FirstOrDefault(x => x?.IsExefs == true); if (ExefsSection == null) { @@ -265,10 +304,12 @@ namespace Ryujinx.HLE.HOS return; } - Stream RomfsStream = Nca.OpenSection(RomfsSection.SectionNum, false); + Stream RomfsStream = MainNca.OpenSection(RomfsSection.SectionNum, false); + Device.FileSystem.SetRomFs(RomfsStream); - Stream ExefsStream = Nca.OpenSection(ExefsSection.SectionNum, false); + Stream ExefsStream = MainNca.OpenSection(ExefsSection.SectionNum, false); + Pfs Exefs = new Pfs(ExefsStream); Npdm MetaData = null; @@ -305,6 +346,35 @@ namespace Ryujinx.HLE.HOS } } + Nacp ReadControlData() + { + Romfs ControlRomfs = new Romfs(ControlNca.OpenSection(0, false)); + + byte[] ControlFile = ControlRomfs.GetFile("/control.nacp"); + + BinaryReader Reader = new BinaryReader(new MemoryStream(ControlFile)); + + Nacp ControlData = new Nacp(Reader); + + CurrentTitle = ControlData.Languages[(int)State.DesiredTitleLanguage].Title; + + if (string.IsNullOrWhiteSpace(CurrentTitle)) + { + CurrentTitle = ControlData.Languages.ToList().Find(x => !string.IsNullOrWhiteSpace(x.Title)).Title; + } + + return ControlData; + } + + if (ControlNca != null) + { + MainProcess.ControlData = ReadControlData(); + } + else + { + CurrentTitle = MainProcess.MetaData.ACI0.TitleId.ToString("x16"); + } + if (!MainProcess.MetaData.Is64Bits) { throw new NotImplementedException("32-bit titles are unsupported!"); diff --git a/Ryujinx.HLE/HOS/Process.cs b/Ryujinx.HLE/HOS/Process.cs index 289920bef3..7900705dfa 100644 --- a/Ryujinx.HLE/HOS/Process.cs +++ b/Ryujinx.HLE/HOS/Process.cs @@ -2,6 +2,7 @@ using ChocolArm64; using ChocolArm64.Events; using ChocolArm64.Memory; using ChocolArm64.State; +using LibHac; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Diagnostics.Demangler; using Ryujinx.HLE.HOS.Kernel; @@ -42,6 +43,8 @@ namespace Ryujinx.HLE.HOS public Npdm MetaData { get; private set; } + public Nacp ControlData { get; set; } + public KProcessHandleTable HandleTable { get; private set; } public AppletStateMgr AppletState { get; private set; } diff --git a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs index bd1dbd78d6..2a3c8288b2 100644 --- a/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs +++ b/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs @@ -37,6 +37,8 @@ namespace Ryujinx.HLE.HOS.SystemState internal long DesiredLanguageCode { get; private set; } + public TitleLanguage DesiredTitleLanguage { get; private set; } + internal string ActiveAudioOutput { get; private set; } public bool DockedMode { get; set; } @@ -64,6 +66,8 @@ namespace Ryujinx.HLE.HOS.SystemState public void SetLanguage(SystemLanguage Language) { DesiredLanguageCode = GetLanguageCode((int)Language); + + DesiredTitleLanguage = Enum.Parse(Enum.GetName(typeof(SystemLanguage), Language)); } public void SetAudioOutputAsTv() diff --git a/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs b/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs new file mode 100644 index 0000000000..f481ac2932 --- /dev/null +++ b/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs @@ -0,0 +1,21 @@ +namespace Ryujinx.HLE.HOS.SystemState +{ + public enum TitleLanguage + { + AmericanEnglish, + BritishEnglish, + Japanese, + French, + German, + LatinAmericanSpanish, + Spanish, + Italian, + Dutch, + CanadianFrench, + Portuguese, + Russian, + Korean, + Taiwanese, + Chinese + } +} diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs index 8adff9c00d..27f3f08b56 100644 --- a/Ryujinx/Ui/GLScreen.cs +++ b/Ryujinx/Ui/GLScreen.cs @@ -258,8 +258,11 @@ namespace Ryujinx double HostFps = Device.Statistics.GetSystemFrameRate(); double GameFps = Device.Statistics.GetGameFrameRate(); - NewTitle = $"Ryujinx | Host FPS: {HostFps:0.0} | Game FPS: {GameFps:0.0} | Game Vsync: " + - (Device.EnableDeviceVsync ? "On" : "Off"); + string TitleSection = string.IsNullOrWhiteSpace(Device.System.CurrentTitle) ? string.Empty + : " | " + Device.System.CurrentTitle; + + NewTitle = $"Ryujinx{TitleSection} | Host FPS: {HostFps:0.0} | Game FPS: {GameFps:0.0} | " + + $"Game Vsync: {(Device.EnableDeviceVsync ? "On" : "Off")}"; TitleEvent = true;