diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs index b53fdd4df..1301aaebe 100644 --- a/src/ARMeilleure/Translation/PTC/Ptc.cs +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -3,6 +3,8 @@ using ARMeilleure.CodeGen.Linking; using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.Common; using ARMeilleure.Memory; +using ARMeilleure.State; +using Humanizer; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; @@ -30,8 +32,8 @@ namespace ARMeilleure.Translation.PTC { private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string InnerHeaderMagicString = "PTCihd\0\0"; - - private const uint InternalVersion = 6998; //! To be incremented manually for each change to the ARMeilleure project. + + private const uint InternalVersion = 7007; //! To be incremented manually for each change to the ARMeilleure project. private const string ActualDir = "0"; private const string BackupDir = "1"; @@ -184,6 +186,36 @@ namespace ARMeilleure.Translation.PTC InitializeCarriers(); } + private bool ContainsBlacklistedFunctions() + { + List blacklist = Profiler.GetBlacklistedFunctions(); + bool containsBlacklistedFunctions = false; + _infosStream.Seek(0L, SeekOrigin.Begin); + bool foundBadFunction = false; + + for (int index = 0; index < GetEntriesCount(); index++) + { + InfoEntry infoEntry = DeserializeStructure(_infosStream); + foreach (ulong address in blacklist) + { + if (infoEntry.Address == address) + { + containsBlacklistedFunctions = true; + Logger.Warning?.Print(LogClass.Ptc, "PPTC cache invalidated: Found blacklisted functions in PPTC cache"); + foundBadFunction = true; + break; + } + } + + if (foundBadFunction) + { + break; + } + } + + return containsBlacklistedFunctions; + } + private void PreLoad() { string fileNameActual = $"{CachePathActual}.cache"; @@ -532,7 +564,7 @@ namespace ARMeilleure.Translation.PTC public void LoadTranslations(Translator translator) { - if (AreCarriersEmpty()) + if (AreCarriersEmpty() || ContainsBlacklistedFunctions()) { return; } @@ -835,10 +867,18 @@ namespace ARMeilleure.Translation.PTC while (profiledFuncsToTranslate.TryDequeue(out (ulong address, PtcProfiler.FuncProfile funcProfile) item)) { ulong address = item.address; + ExecutionMode executionMode = item.funcProfile.Mode; + bool highCq = item.funcProfile.HighCq; Debug.Assert(Profiler.IsAddressInStaticCodeRange(address)); - TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq); + TranslatedFunction func = translator.Translate(address, executionMode, highCq); + + if (func == null) + { + Profiler.UpdateEntry(address, executionMode, true, true); + continue; + } bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func); @@ -884,8 +924,11 @@ namespace ARMeilleure.Translation.PTC sw.Stop(); PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount); - - Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s"); + + Logger.Info?.Print(LogClass.Ptc, + $"{_translateCount} of {_translateTotalCount} functions translated in {sw.Elapsed.TotalSeconds} seconds " + + $"| {"function".ToQuantity(_translateTotalCount - _translateCount)} blacklisted " + + $"| Thread count: {degreeOfParallelism}"); Thread preSaveThread = new(PreSave) { diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs index 21987f72d..de0b78dbe 100644 --- a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs +++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -24,11 +24,12 @@ namespace ARMeilleure.Translation.PTC { private const string OuterHeaderMagicString = "Pohd\0\0\0\0"; - private const uint InternalVersion = 5518; //! Not to be incremented manually for each change to the ARMeilleure project. + private const uint InternalVersion = 7007; //! Not to be incremented manually for each change to the ARMeilleure project. - private static readonly uint[] _migrateInternalVersions = + private static readonly uint[] _migrateInternalVersions = [ - 1866 + 1866, + 5518, ]; private const int SaveInterval = 30; // Seconds. @@ -77,20 +78,30 @@ namespace ARMeilleure.Translation.PTC private void TimerElapsed(object _, ElapsedEventArgs __) => new Thread(PreSave) { Name = "Ptc.DiskWriter" }.Start(); - public void AddEntry(ulong address, ExecutionMode mode, bool highCq) + public void AddEntry(ulong address, ExecutionMode mode, bool highCq, bool blacklist = false) { if (IsAddressInStaticCodeRange(address)) { Debug.Assert(!highCq); - lock (_lock) + if (blacklist) { - ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false)); + lock (_lock) + { + ProfiledFuncs[address] = new FuncProfile(mode, highCq: false, true); + } + } + else + { + lock (_lock) + { + ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false, false)); + } } } } - public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq) + public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq, bool? blacklist = null) { if (IsAddressInStaticCodeRange(address)) { @@ -100,7 +111,7 @@ namespace ARMeilleure.Translation.PTC { Debug.Assert(ProfiledFuncs.ContainsKey(address)); - ProfiledFuncs[address] = new FuncProfile(mode, highCq: true); + ProfiledFuncs[address] = new FuncProfile(mode, highCq: true, blacklist ?? ProfiledFuncs[address].Blacklist); } } } @@ -116,7 +127,7 @@ namespace ARMeilleure.Translation.PTC foreach (KeyValuePair profiledFunc in ProfiledFuncs) { - if (!funcs.ContainsKey(profiledFunc.Key)) + if (!funcs.ContainsKey(profiledFunc.Key) && !profiledFunc.Value.Blacklist) { profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value)); } @@ -131,6 +142,24 @@ namespace ARMeilleure.Translation.PTC ProfiledFuncs.TrimExcess(); } + public List GetBlacklistedFunctions() + { + List funcs = new List(); + + foreach (var profiledFunc in ProfiledFuncs) + { + if (profiledFunc.Value.Blacklist) + { + if (!funcs.Contains(profiledFunc.Key)) + { + funcs.Add(profiledFunc.Key); + } + } + } + + return funcs; + } + public void PreLoad() { _lastHash = default; @@ -221,13 +250,18 @@ namespace ARMeilleure.Translation.PTC return false; } + Func migrateEntryFunc = null; + switch (outerHeader.InfoFileVersion) { case InternalVersion: ProfiledFuncs = Deserialize(stream); break; case 1866: - ProfiledFuncs = Deserialize(stream, (address, profile) => (address + 0x500000UL, profile)); + migrateEntryFunc = (address, profile) => (address + 0x500000UL, profile); + goto case 5518; + case 5518: + ProfiledFuncs = DeserializeAddBlacklist(stream, migrateEntryFunc); break; default: Logger.Error?.Print(LogClass.Ptc, $"No migration path for {nameof(outerHeader.InfoFileVersion)} '{outerHeader.InfoFileVersion}'. Discarding cache."); @@ -257,6 +291,16 @@ namespace ARMeilleure.Translation.PTC return DeserializeDictionary(stream, DeserializeStructure); } + private static Dictionary DeserializeAddBlacklist(Stream stream, Func migrateEntryFunc = null) + { + if (migrateEntryFunc != null) + { + return DeserializeAndUpdateDictionary(stream, (Stream stream) => { return new FuncProfile(DeserializeStructure(stream)); }, migrateEntryFunc); + } + + return DeserializeDictionary(stream, (Stream stream) => { return new FuncProfile(DeserializeStructure(stream)); }); + } + private static ReadOnlySpan GetReadOnlySpan(MemoryStream memoryStream) { return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position); @@ -388,13 +432,35 @@ namespace ARMeilleure.Translation.PTC } } - [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)] + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 6*/)] public struct FuncProfile { public ExecutionMode Mode; public bool HighCq; + public bool Blacklist; - public FuncProfile(ExecutionMode mode, bool highCq) + public FuncProfile(ExecutionMode mode, bool highCq, bool blacklist) + { + Mode = mode; + HighCq = highCq; + Blacklist = blacklist; + } + + public FuncProfile(FuncProfilePreBlacklist fp) + { + Mode = fp.Mode; + HighCq = fp.HighCq; + Blacklist = false; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)] + public struct FuncProfilePreBlacklist + { + public ExecutionMode Mode; + public bool HighCq; + + public FuncProfilePreBlacklist(ExecutionMode mode, bool highCq) { Mode = mode; HighCq = highCq; diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs index 0f18c8045..a50702add 100644 --- a/src/ARMeilleure/Translation/Translator.cs +++ b/src/ARMeilleure/Translation/Translator.cs @@ -249,6 +249,11 @@ namespace ARMeilleure.Translation ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter counter); + if (cfg == null) + { + return null; + } + ulong funcSize = funcRange.End - funcRange.Start; Logger.EndPass(PassName.Translation, cfg); @@ -407,6 +412,11 @@ namespace ARMeilleure.Translation if (opCode.Instruction.Emitter != null) { opCode.Instruction.Emitter(context); + if (opCode.Instruction.Name == InstName.Und && blkIndex == 0) + { + range = new Range(rangeStart, rangeEnd); + return null; + } } else { diff --git a/src/Ryujinx.Graphics.Metal/HardwareInfo.cs b/src/Ryujinx.Graphics.Metal/HardwareInfo.cs index 413fabf09..f6a132f09 100644 --- a/src/Ryujinx.Graphics.Metal/HardwareInfo.cs +++ b/src/Ryujinx.Graphics.Metal/HardwareInfo.cs @@ -76,7 +76,7 @@ namespace Ryujinx.Graphics.Metal return model; } - return ""; + return string.Empty; } } } diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Declarations.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Declarations.cs index c779f5e8d..081dd516b 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Declarations.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Declarations.cs @@ -218,7 +218,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl foreach (MemoryDefinition memory in memories) { - string arraySize = ""; + string arraySize = string.Empty; if ((memory.Type & AggregateType.Array) != 0) { arraySize = $"[{memory.ArrayLength}]"; @@ -240,7 +240,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl BufferDefinition buffer = buffers[i]; bool needsPadding = buffer.Layout == BufferLayout.Std140; - string fsiSuffix = !constant && fsi ? " [[raster_order_group(0)]]" : ""; + string fsiSuffix = !constant && fsi ? " [[raster_order_group(0)]]" : string.Empty; bufferDec[i] = $"{addressSpace} {Defaults.StructPrefix}_{buffer.Name}* {buffer.Name}{fsiSuffix};"; @@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl type &= ~AggregateType.Array; string typeName = GetVarTypeName(type); - string arraySuffix = ""; + string arraySuffix = string.Empty; if (field.Type.HasFlag(AggregateType.Array)) { @@ -353,7 +353,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl imageTypeName = $"array<{imageTypeName}, {image.ArrayLength}>"; } - string fsiSuffix = fsi ? " [[raster_order_group(0)]]" : ""; + string fsiSuffix = fsi ? " [[raster_order_group(0)]]" : string.Empty; imageDec[i] = $"{imageTypeName} {image.Name}{fsiSuffix};"; } @@ -454,7 +454,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl IoVariable.VertexIndex => "[[vertex_id]]", // IoVariable.PointCoord => "[[point_coord]]", IoVariable.UserDefined => context.Definitions.Stage == ShaderStage.Fragment ? $"[[user(loc{ioDefinition.Location})]]" : $"[[attribute({ioDefinition.Location})]]", - _ => "" + _ => string.Empty }; context.AppendLine($"{type} {name} {iq}{suffix};"); @@ -545,7 +545,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl IoVariable.FragmentOutputColor => $"[[color({ioDefinition.Location})]]", IoVariable.FragmentOutputDepth => "[[depth(any)]]", IoVariable.ClipDistance => $"[[clip_distance]][{Defaults.TotalClipDistances}]", - _ => "" + _ => string.Empty }; context.AppendLine($"{type} {name} {suffix};"); diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/InstGenMemory.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/InstGenMemory.cs index b3a995c6a..a84a75e4f 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/InstGenMemory.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/Instructions/InstGenMemory.cs @@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions inputsCount--; } - string fieldName = ""; + string fieldName = string.Empty; switch (storageKind) { case StorageKind.ConstantBuffer: @@ -140,7 +140,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions } } varName += fieldName; - varName += fieldHasPadding ? ".x" : ""; + varName += fieldHasPadding ? ".x" : string.Empty; if (isStore) { @@ -434,7 +434,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions string prefix = intCoords ? "uint" : "float"; - return prefix + (count > 1 ? count : "") + "(" + coords + ")"; + return prefix + (count > 1 ? count : string.Empty) + "(" + coords + ")"; } Append(AssemblePVector(pCount)); @@ -504,7 +504,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions } texCallBuilder.Append(')'); - texCallBuilder.Append(colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); + texCallBuilder.Append(colorIsVector ? GetMaskMultiDest(texOp.Index) : string.Empty); return texCallBuilder.ToString(); } @@ -558,7 +558,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl.Instructions { if (mask == 0x0) { - return ""; + return string.Empty; } string swizzle = "."; diff --git a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/MslGenerator.cs b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/MslGenerator.cs index bc38ea26b..8ca24fcd3 100644 --- a/src/Ryujinx.Graphics.Shader/CodeGen/Msl/MslGenerator.cs +++ b/src/Ryujinx.Graphics.Shader/CodeGen/Msl/MslGenerator.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Msl if (parameters.Definitions.Stage is not (ShaderStage.Vertex or ShaderStage.Fragment or ShaderStage.Compute)) { Logger.Warning?.Print(LogClass.Gpu, $"Attempted to generate unsupported shader type {parameters.Definitions.Stage}!"); - return ""; + return string.Empty; } CodeGenContext context = new(info, parameters); diff --git a/src/Ryujinx.Graphics.Shader/SamplerType.cs b/src/Ryujinx.Graphics.Shader/SamplerType.cs index 20352d13e..f81d512fa 100644 --- a/src/Ryujinx.Graphics.Shader/SamplerType.cs +++ b/src/Ryujinx.Graphics.Shader/SamplerType.cs @@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Shader _ => "float" }; - return $"{typeName}<{format}{(image ? ", access::read_write" : "")}>"; + return $"{typeName}<{format}{(image ? ", access::read_write" : string.Empty)}>"; } } } diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs index 8ac76508f..0b7ae3974 100644 --- a/src/Ryujinx.HLE/HLEConfiguration.cs +++ b/src/Ryujinx.HLE/HLEConfiguration.cs @@ -192,6 +192,7 @@ namespace Ryujinx.HLE /// /// The desired hacky workarounds. /// + /// This cannot be changed after instantiation. public EnabledDirtyHack[] Hacks { internal get; set; } public HLEConfiguration(VirtualFileSystem virtualFileSystem, diff --git a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs index 95b6167f3..08d929bf0 100644 --- a/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs +++ b/src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs @@ -20,6 +20,7 @@ namespace Ryujinx.HLE.HOS private readonly string _titleIdText; private readonly string _displayVersion; private readonly bool _diskCacheEnabled; + private readonly string _diskCacheSelector; private readonly ulong _codeAddress; private readonly ulong _codeSize; @@ -31,6 +32,7 @@ namespace Ryujinx.HLE.HOS string titleIdText, string displayVersion, bool diskCacheEnabled, + string diskCacheSelector, ulong codeAddress, ulong codeSize) { @@ -39,6 +41,7 @@ namespace Ryujinx.HLE.HOS _titleIdText = titleIdText; _displayVersion = displayVersion; _diskCacheEnabled = diskCacheEnabled; + _diskCacheSelector = diskCacheSelector; _codeAddress = codeAddress; _codeSize = codeSize; } @@ -114,7 +117,7 @@ namespace Ryujinx.HLE.HOS } } - DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, "default"); //Ready for exefs profiles + DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, _diskCacheSelector ?? "default"); return processContext; } diff --git a/src/Ryujinx.HLE/HOS/ModLoader.cs b/src/Ryujinx.HLE/HOS/ModLoader.cs index ff691914c..0f2e09da6 100644 --- a/src/Ryujinx.HLE/HOS/ModLoader.cs +++ b/src/Ryujinx.HLE/HOS/ModLoader.cs @@ -6,6 +6,7 @@ using LibHac.Loader; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.RomFs; +using LibHac.Util; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; @@ -19,6 +20,7 @@ using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; +using System.Security.Cryptography; using LazyFile = Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.LazyFile; using Path = System.IO.Path; @@ -581,6 +583,7 @@ namespace Ryujinx.HLE.HOS public BitVector32 Stubs; public BitVector32 Replaces; public MetaLoader Npdm; + public string Hash; public bool Modified => (Stubs.Data | Replaces.Data) != 0; } @@ -591,8 +594,11 @@ namespace Ryujinx.HLE.HOS { Stubs = new BitVector32(), Replaces = new BitVector32(), + Hash = null, }; + string tempHash = string.Empty; + if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsDirs.Count == 0) { return modLoadResult; @@ -628,8 +634,16 @@ namespace Ryujinx.HLE.HOS modLoadResult.Replaces[1 << i] = true; - nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName); - Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced"); + using (FileStream stream = nsoFile.OpenRead()) + { + nsos[i] = new NsoExecutable(stream.AsStorage(), nsoName); + Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced"); + using (MD5 md5 = MD5.Create()) + { + stream.Seek(0, SeekOrigin.Begin); + tempHash += BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); + } + } } modLoadResult.Stubs[1 << i] |= File.Exists(Path.Combine(mod.Path.FullName, nsoName + StubExtension)); @@ -661,6 +675,14 @@ namespace Ryujinx.HLE.HOS } } + if (!string.IsNullOrEmpty(tempHash)) + { + using (MD5 md5 = MD5.Create()) + { + modLoadResult.Hash += BitConverter.ToString(md5.ComputeHash(tempHash.ToBytes())).Replace("-", string.Empty).ToLowerInvariant(); + } + } + return modLoadResult; } diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs index 517359e70..712967180 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/LdnRyu/LdnMasterProxyClient.cs @@ -121,7 +121,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu private void UpdatePassphraseIfNeeded() { - string passphrase = _config.MultiplayerLdnPassphrase ?? ""; + string passphrase = _config.MultiplayerLdnPassphrase ?? string.Empty; if (passphrase != _passphrase) { _passphrase = passphrase; diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs index 5874636e7..01f65206f 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs @@ -84,13 +84,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions // Apply Nsos patches. device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables); - // Don't use PTC if ExeFS files have been replaced. - bool enablePtc = device.System.EnablePtc && !modLoadResult.Modified; - if (!enablePtc) - { - Logger.Warning?.Print(LogClass.Ptc, "Detected unsupported ExeFs modifications. PTC disabled."); - } - string programName = string.Empty; if (!isHomebrew && programId > 0x010000000000FFFF) @@ -117,7 +110,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions device.System.KernelContext, metaLoader, nacpData, - enablePtc, + device.System.EnablePtc, + modLoadResult.Hash, true, programName, metaLoader.GetProgramId(), diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index 726b017b6..4c0866531 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -235,6 +235,7 @@ namespace Ryujinx.HLE.Loaders.Processes dummyExeFs.GetNpdm(), nacpData, diskCacheEnabled: false, + diskCacheSelector: null, allowCodeMemoryForJit: true, programName, programId, diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index b11057da2..badced1b9 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -186,6 +186,7 @@ namespace Ryujinx.HLE.Loaders.Processes string.Empty, string.Empty, false, + null, codeAddress, codeSize); @@ -226,6 +227,7 @@ namespace Ryujinx.HLE.Loaders.Processes MetaLoader metaLoader, BlitStruct applicationControlProperties, bool diskCacheEnabled, + string diskCacheSelector, bool allowCodeMemoryForJit, string name, ulong programId, @@ -379,6 +381,7 @@ namespace Ryujinx.HLE.Loaders.Processes $"{programId:x16}", displayVersion, diskCacheEnabled, + diskCacheSelector, codeStart, codeSize); diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs index 3a7042670..6fd9408ed 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs @@ -88,7 +88,7 @@ namespace Ryujinx.HLE.Loaders.Processes bool isFirmwareApplication = ProgramId <= 0x0100000000007FFF; string name = !isFirmware - ? (isFirmwareApplication ? "Firmware Application " : "") + (!string.IsNullOrWhiteSpace(Name) ? Name : "") + ? (isFirmwareApplication ? "Firmware Application " : string.Empty) + (!string.IsNullOrWhiteSpace(Name) ? Name : "") : "Firmware"; // TODO: LibHac npdm currently doesn't support version field. diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index d96682956..4706abc68 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -2022,6 +2022,56 @@ "zh_TW": "下一次啟動遊戲時,觸發 PPTC 進行重建" } }, + { + "ID": "GameListContextMenuCacheManagementNukePptc", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Purge PPTC cache", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "GameListContextMenuCacheManagementNukePptcToolTip", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Deletes all PPTC cache files for the Application", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "GameListContextMenuCacheManagementPurgeShaderCache", "Translations": { @@ -7718,7 +7768,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "颜色", "zh_TW": "" } }, @@ -12947,6 +12997,31 @@ "zh_TW": "在 {0} 清除 PPTC 快取時出錯: {1}" } }, + { + "ID": "DialogPPTCNukeMessage", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "You are about to purge all PPTC data from:\n\n{0}\n\nAre you sure you want to proceed?", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, { "ID": "DialogShaderDeletionMessage", "Translations": { @@ -18943,7 +19018,7 @@ "th_TH": "", "tr_TR": "", "uk_UA": "", - "zh_CN": "", + "zh_CN": "LED 设置", "zh_TW": "" } }, diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.cs b/src/Ryujinx/Headless/HeadlessRyujinx.cs index 18efdceee..fafcbf01e 100644 --- a/src/Ryujinx/Headless/HeadlessRyujinx.cs +++ b/src/Ryujinx/Headless/HeadlessRyujinx.cs @@ -5,6 +5,7 @@ using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.Logging.Targets; @@ -26,6 +27,7 @@ using Ryujinx.SDL2.Common; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading; namespace Ryujinx.Headless @@ -286,6 +288,9 @@ namespace Ryujinx.Headless GraphicsConfig.EnableMacroHLE = !option.DisableMacroHLE; DriverUtilities.InitDriverConfig(option.BackendThreading == BackendThreading.Off); + + if (_inputConfiguration.OfType().Any(ic => ic.Led.UseRainbow)) + Rainbow.Enable(); while (true) { diff --git a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml index ed22fb088..2816afbce 100644 --- a/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml +++ b/src/Ryujinx/UI/Applet/UserSelectorDialog.axaml @@ -13,11 +13,6 @@ mc:Ignorable="d" Focusable="True" x:DataType="viewModels:UserSelectorDialogViewModel"> - - - - - @@ -80,7 +75,7 @@ Height="96" HorizontalAlignment="Stretch" VerticalAlignment="Top" - Source="{Binding Image, Converter={StaticResource ByteImage}}" /> + Source="{Binding Image, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" /> - - diff --git a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml index 9fed95aa7..475b26787 100644 --- a/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationContextMenu.axaml @@ -81,6 +81,11 @@ Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}" Icon="{ext:Icon mdi-refresh}" ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> + cacheFiles = new(); + + if (mainDir.Exists) + { + cacheFiles.AddRange(mainDir.EnumerateFiles("*.cache")); + cacheFiles.AddRange(mainDir.EnumerateFiles("*.info")); + } + + if (backupDir.Exists) + { + cacheFiles.AddRange(backupDir.EnumerateFiles("*.cache")); + cacheFiles.AddRange(mainDir.EnumerateFiles("*.info")); + } + + if (cacheFiles.Count > 0) + { + foreach (FileInfo file in cacheFiles) + { + try + { + file.Delete(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionErrorMessage, file.Name, ex)); + } + } + } + } + } + public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args) { if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) diff --git a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml index 629bdebbf..7f7e3260b 100644 --- a/src/Ryujinx/UI/Controls/ApplicationGridView.axaml +++ b/src/Ryujinx/UI/Controls/ApplicationGridView.axaml @@ -13,9 +13,6 @@ mc:Ignorable="d" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" x:DataType="viewModels:MainWindowViewModel"> - - - @@ -68,7 +65,7 @@ Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Top" - Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> + Source="{Binding Icon, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" /> - - - @@ -62,7 +59,7 @@ Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" - Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> + Source="{Binding Icon, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" /> values, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs index d20098426..e7a157892 100644 --- a/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/KeyValueConverter.cs @@ -10,7 +10,7 @@ namespace Ryujinx.Ava.UI.Helpers { internal class KeyValueConverter : IValueConverter { - public static KeyValueConverter Instance = new(); + public static readonly KeyValueConverter Instance = new(); private static readonly Dictionary _keysMap = new() { diff --git a/src/Ryujinx/UI/Helpers/Converters/MultiplayerInfoConverter.cs b/src/Ryujinx/UI/Helpers/Converters/MultiplayerInfoConverter.cs index 09a2ad367..47d0b94d0 100644 --- a/src/Ryujinx/UI/Helpers/Converters/MultiplayerInfoConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/MultiplayerInfoConverter.cs @@ -1,6 +1,5 @@ using Avalonia.Data.Converters; using Avalonia.Markup.Xaml; -using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Utilities.AppLibrary; using System; using System.Globalization; @@ -19,15 +18,10 @@ namespace Ryujinx.Ava.UI.Helpers { return $"Hosted Games: {applicationData.GameCount}\nOnline Players: {applicationData.PlayerCount}"; } - else - { - return ""; - } - } - else - { - return ""; } + + return ""; + } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) diff --git a/src/Ryujinx/UI/Helpers/Converters/TitleUpdateLabelConverter.cs b/src/Ryujinx/UI/Helpers/Converters/TitleUpdateLabelConverter.cs index d462b9463..f1e6b8958 100644 --- a/src/Ryujinx/UI/Helpers/Converters/TitleUpdateLabelConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/TitleUpdateLabelConverter.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Ava.UI.Helpers { internal class TitleUpdateLabelConverter : IMultiValueConverter { - public static TitleUpdateLabelConverter Instance = new(); + public static readonly TitleUpdateLabelConverter Instance = new(); public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileSpaceSavingsConverter.cs b/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileSpaceSavingsConverter.cs index ab6199c3f..d70a795c0 100644 --- a/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileSpaceSavingsConverter.cs +++ b/src/Ryujinx/UI/Helpers/Converters/XCITrimmerFileSpaceSavingsConverter.cs @@ -12,7 +12,7 @@ namespace Ryujinx.Ava.UI.Helpers internal class XCITrimmerFileSpaceSavingsConverter : IValueConverter { private const long _bytesPerMB = 1024 * 1024; - public static XCITrimmerFileSpaceSavingsConverter Instance = new(); + public static readonly XCITrimmerFileSpaceSavingsConverter Instance = new(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Ryujinx/UI/Models/CheatNode.cs b/src/Ryujinx/UI/Models/CheatNode.cs index 4fc249e20..b036ba6b9 100644 --- a/src/Ryujinx/UI/Models/CheatNode.cs +++ b/src/Ryujinx/UI/Models/CheatNode.cs @@ -9,7 +9,7 @@ namespace Ryujinx.Ava.UI.Models { private bool _isEnabled = false; public ObservableCollection SubNodes { get; } = []; - public string CleanName => Name[1..^7]; + public string CleanName => Name.Length > 0 ? Name[1..^7] : Name; public string BuildIdKey => $"{BuildId}-{Name}"; public bool IsRootNode { get; } public string Name { get; } diff --git a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml index 04f07e437..c592c2b14 100644 --- a/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx/UI/Views/Input/ControllerInputView.axaml @@ -20,9 +20,6 @@ - - -