Compare commits

...

13 Commits

Author SHA1 Message Date
LotP1
49ed30fa2a Update locales.json 2025-01-20 01:31:50 +01:00
LotP1
d96f89ec37 Merge remote-tracking branch 'upstream/master' into PPTC-profiles 2025-01-20 01:27:07 +01:00
Evan Husted
6482e566ab UI: Compat: Unload compatibility entries when the window closes. 2025-01-19 17:41:50 -06:00
Evan Husted
7fcd9b792e UI: Compat: Update owned game title IDs when ApplicationLibrary app count updates 2025-01-19 17:41:31 -06:00
Evan Husted
e676fd8b17 UI: misc: simplify Intel Mac warning logic 2025-01-19 14:42:15 -06:00
Evan Husted
dd16e3cee1 misc: chore: very small cleanup in AvaHostUIHandler 2025-01-19 13:18:40 -06:00
Evan Husted
31e5f74e05 UI: misc: Replace spaces in Title with newlines when using custom title bar (since the Title is in an Avalonia tooltip) 2025-01-19 13:05:20 -06:00
LotP1
e0acefeeef default value of dishCacheSelector should be null 2024-12-20 13:33:46 +01:00
LotP1
b5604311cb Add nuke PPTC option to cache management 2024-12-20 13:33:46 +01:00
LotP1
2c53242b31 Remove outdated comment 2024-12-20 13:31:09 +01:00
LotP1
2874c40ee9 Implement blacklist functionality
- Added blacklist status to FunctionProfile
- Added PPTC Info file updater from v5518 and updated old updater logic to allow multiple update passes
- Added blacklist check to PPTC Cache loading
- Added marking functions as blacklisted if they do not yet exist at PPTC translation time
- Logger now shows how many functions were blacklisted when translating new functions to PPTC cache
2024-12-20 13:31:09 +01:00
LotP1
91518acf30 Fix incorrect hash logic
The stream hadn't been reset causing all hashes to be the same in most cases
2024-12-20 13:31:09 +01:00
LotP1
2af9a33979 Add cacheselector and allow PPTC with exefs mods
this is currently broken with Exlaunch mods that use hooks
2024-12-20 13:31:09 +01:00
19 changed files with 364 additions and 67 deletions

View File

@@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Linking;
using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Common;
using ARMeilleure.Memory;
using ARMeilleure.State;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
@@ -29,8 +30,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";
@@ -183,6 +184,36 @@ namespace ARMeilleure.Translation.PTC
InitializeCarriers();
}
private bool ContainsBlacklistedFunctions()
{
List<ulong> blacklist = Profiler.GetBlacklistedFunctions();
bool containsBlacklistedFunctions = false;
_infosStream.Seek(0L, SeekOrigin.Begin);
bool foundBadFunction = false;
for (int index = 0; index < GetEntriesCount(); index++)
{
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_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";
@@ -531,7 +562,7 @@ namespace ARMeilleure.Translation.PTC
public void LoadTranslations(Translator translator)
{
if (AreCarriersEmpty())
if (AreCarriersEmpty() || ContainsBlacklistedFunctions())
{
return;
}
@@ -834,10 +865,18 @@ namespace ARMeilleure.Translation.PTC
while (profiledFuncsToTranslate.TryDequeue(out var 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,7 +923,14 @@ namespace ARMeilleure.Translation.PTC
PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount);
Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s");
if (_translateCount == _translateTotalCount)
{
Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s");
}
else
{
Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | {_translateTotalCount - _translateCount} function{(_translateTotalCount - _translateCount != 1 ? "s" : "")} blacklisted | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s");
}
Thread preSaveThread = new(PreSave)
{

View File

@@ -24,10 +24,11 @@ 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 = {
1866,
5518,
};
private const int SaveInterval = 30; // Seconds.
@@ -76,20 +77,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))
{
@@ -99,7 +110,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);
}
}
}
@@ -115,7 +126,7 @@ namespace ARMeilleure.Translation.PTC
foreach (var profiledFunc in ProfiledFuncs)
{
if (!funcs.ContainsKey(profiledFunc.Key))
if (!funcs.ContainsKey(profiledFunc.Key) && !profiledFunc.Value.Blacklist)
{
profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value));
}
@@ -130,6 +141,24 @@ namespace ARMeilleure.Translation.PTC
ProfiledFuncs.TrimExcess();
}
public List<ulong> GetBlacklistedFunctions()
{
List<ulong> funcs = new List<ulong>();
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;
@@ -220,13 +249,18 @@ namespace ARMeilleure.Translation.PTC
return false;
}
Func<ulong, FuncProfile, (ulong, FuncProfile)> 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.");
@@ -256,6 +290,16 @@ namespace ARMeilleure.Translation.PTC
return DeserializeDictionary<ulong, FuncProfile>(stream, DeserializeStructure<FuncProfile>);
}
private static Dictionary<ulong, FuncProfile> DeserializeAddBlacklist(Stream stream, Func<ulong, FuncProfile, (ulong, FuncProfile)> migrateEntryFunc = null)
{
if (migrateEntryFunc != null)
{
return DeserializeAndUpdateDictionary(stream, (Stream stream) => { return new FuncProfile(DeserializeStructure<FuncProfilePreBlacklist>(stream)); }, migrateEntryFunc);
}
return DeserializeDictionary<ulong, FuncProfile>(stream, (Stream stream) => { return new FuncProfile(DeserializeStructure<FuncProfilePreBlacklist>(stream)); });
}
private static ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
{
return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position);
@@ -387,13 +431,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;

View File

@@ -249,6 +249,11 @@ namespace ARMeilleure.Translation
ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> 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
{

View File

@@ -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;
}

View File

@@ -5,6 +5,7 @@ using LibHac.FsSystem;
using LibHac.Loader;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.RomFs;
using LibHac.Util;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
@@ -18,6 +19,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;
@@ -580,6 +582,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;
}
@@ -590,8 +593,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;
@@ -627,8 +633,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));
@@ -660,6 +674,14 @@ namespace Ryujinx.HLE.HOS
}
}
if (!string.IsNullOrEmpty(tempHash))
{
using (MD5 md5 = MD5.Create())
{
modLoadResult.Hash += BitConverter.ToString(md5.ComputeHash(tempHash.ToBytes())).Replace("-", "").ToLowerInvariant();
}
}
return modLoadResult;
}

View File

@@ -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(),

View File

@@ -235,6 +235,7 @@ namespace Ryujinx.HLE.Loaders.Processes
dummyExeFs.GetNpdm(),
nacpData,
diskCacheEnabled: false,
diskCacheSelector: null,
allowCodeMemoryForJit: true,
programName,
programId,

View File

@@ -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<ApplicationControlProperty> 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);

View File

@@ -489,7 +489,7 @@ namespace Ryujinx.Ava
Dispatcher.UIThread.InvokeAsync(() =>
{
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version);
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowTitleBar);
});
_viewModel.SetUiProgressHandlers(Device);
@@ -872,7 +872,7 @@ namespace Ryujinx.Ava
Device?.System.TogglePauseEmulation(false);
_viewModel.IsPaused = false;
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version);
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowTitleBar);
Logger.Info?.Print(LogClass.Emulation, "Emulation was resumed");
}
@@ -881,7 +881,7 @@ namespace Ryujinx.Ava
Device?.System.TogglePauseEmulation(true);
_viewModel.IsPaused = true;
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, LocaleManager.Instance[LocaleKeys.Paused]);
_viewModel.Title = TitleHelper.ActiveApplicationTitle(Device?.Processes.ActiveApplication, Program.Version, !ConfigurationState.Instance.ShowTitleBar, LocaleManager.Instance[LocaleKeys.Paused]);
Logger.Info?.Print(LogClass.Emulation, "Emulation was paused");
}

View File

@@ -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": {
@@ -12797,6 +12847,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": {
@@ -22898,4 +22973,4 @@
}
}
]
}
}

View File

@@ -2,6 +2,7 @@ using DiscordRPC;
using Gommon;
using Humanizer;
using Humanizer.Localisation;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common;
@@ -97,7 +98,7 @@ namespace Ryujinx.Ava
},
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false, maxUnit: TimeUnit.Hour)}"
? $"Total play time: {ValueFormatUtils.FormatTimeSpan(appMeta.TimePlayed)}"
: "Never played",
Timestamps = Timestamps.Now
});

View File

@@ -279,13 +279,13 @@ namespace Ryujinx.Ava.UI.Applet
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
profiles.Add(new Models.UserProfile(guest, nav));
UserSelectorDialogViewModel viewModel = new();
viewModel.Profiles = profiles;
viewModel.SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId;
UserSelectorDialogViewModel viewModel = new()
{
Profiles = profiles,
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
};
UserSelectorDialog content = new(viewModel);
(UserId id, _) = await UserSelectorDialog.ShowInputDialog(content);
selected = id;
(selected, _) = await UserSelectorDialog.ShowInputDialog(content);
dialogCloseEvent.Set();
});

View File

@@ -81,6 +81,11 @@
Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}"
Icon="{ext:Icon mdi-refresh}"
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem
Click="NukePtcCache_Click"
Header="{ext:Locale GameListContextMenuCacheManagementNukePptc}"
Icon="{ext:Icon mdi-delete-alert}"
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementNukePptcToolTip}" />
<MenuItem
Click="PurgeShaderCache_Click"
Header="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCache}"

View File

@@ -171,6 +171,52 @@ namespace Ryujinx.Ava.UI.Controls
}
}
public async void NukePtcCache_Click(object sender, RoutedEventArgs args)
{
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
return;
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogWarning],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCNukeMessage, viewModel.SelectedApplication.Name)
);
if (result == UserResult.Yes)
{
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, viewModel.SelectedApplication.IdString, "cache", "cpu", "1"));
List<FileInfo> 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 })

View File

@@ -735,21 +735,19 @@ namespace Ryujinx.Ava.UI.Windows
});
}
private static bool _intelMacWarningShown;
private static bool _intelMacWarningShown = !(OperatingSystem.IsMacOS() &&
(RuntimeInformation.OSArchitecture == Architecture.X64 ||
RuntimeInformation.OSArchitecture == Architecture.X86));
public static async Task ShowIntelMacWarningAsync()
{
if (!_intelMacWarningShown &&
(OperatingSystem.IsMacOS() &&
(RuntimeInformation.OSArchitecture == Architecture.X64 ||
RuntimeInformation.OSArchitecture == Architecture.X86)))
{
_intelMacWarningShown = true;
if (_intelMacWarningShown) return;
await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog(
"Intel Mac Warning",
"Intel Macs are not supported and will not work properly.\nIf you continue, do not come to our Discord asking for support;\nand do not report bugs on the GitHub. They will be closed."));
await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog(
"Intel Mac Warning",
"Intel Macs are not supported and will not work properly.\nIf you continue, do not come to our Discord asking for support."));
}
_intelMacWarningShown = true;
}
}
}

View File

@@ -28,7 +28,9 @@ namespace Ryujinx.Ava.Utilities.Compat
public class CompatibilityCsv
{
static CompatibilityCsv()
static CompatibilityCsv() => Load();
public static void Load()
{
using Stream csvStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("RyujinxGameCompatibilityList")!;
@@ -37,15 +39,31 @@ namespace Ryujinx.Ava.Utilities.Compat
using SepReader reader = Sep.Reader().From(csvStream);
ColumnIndices columnIndices = new(reader.Header.IndexOf);
Entries = reader
_entries = reader
.Enumerate(row => new CompatibilityEntry(ref columnIndices, row))
.OrderBy(it => it.GameName)
.ToArray();
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatCsv");
Logger.Debug?.Print(LogClass.UI, "Compatibility CSV loaded.", "LoadCompatibility");
}
public static CompatibilityEntry[] Entries { get; private set; }
public static void Unload()
{
_entries = null;
}
private static CompatibilityEntry[] _entries;
public static CompatibilityEntry[] Entries
{
get
{
if (_entries == null)
Load();
return _entries;
}
}
}
public class CompatibilityEntry

View File

@@ -1,11 +1,8 @@
using Avalonia.Controls;
using Avalonia.Styling;
using FluentAvalonia.UI.Controls;
using nietras.SeparatedValues;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Utilities.Compat
@@ -35,6 +32,8 @@ namespace Ryujinx.Ava.Utilities.Compat
contentDialog.Styles.Add(closeButtonParent);
await ContentDialogHelper.ShowAsync(contentDialog);
CompatibilityCsv.Unload();
}
public CompatibilityList()

View File

@@ -1,19 +1,18 @@
using CommunityToolkit.Mvvm.ComponentModel;
using ExCSS;
using Gommon;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.Ava.Utilities.Compat
{
public partial class CompatibilityViewModel : ObservableObject
public class CompatibilityViewModel : BaseModel
{
[ObservableProperty] private bool _onlyShowOwnedGames = true;
private bool _onlyShowOwnedGames = true;
private IEnumerable<CompatibilityEntry> _currentEntries = CompatibilityCsv.Entries;
private readonly string[] _ownedGameTitleIds = [];
private readonly ApplicationLibrary _appLibrary;
private string[] _ownedGameTitleIds = [];
public IEnumerable<CompatibilityEntry> CurrentEntries => OnlyShowOwnedGames
? _currentEntries.Where(x =>
@@ -24,14 +23,23 @@ namespace Ryujinx.Ava.Utilities.Compat
public CompatibilityViewModel(ApplicationLibrary appLibrary)
{
_appLibrary = appLibrary;
appLibrary.ApplicationCountUpdated += (_, _)
=> _ownedGameTitleIds = appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
_ownedGameTitleIds = appLibrary.Applications.Keys.Select(x => x.ToString("X16")).ToArray();
}
PropertyChanged += (_, args) =>
public bool OnlyShowOwnedGames
{
get => _onlyShowOwnedGames;
set
{
if (args.PropertyName is nameof(OnlyShowOwnedGames))
OnPropertyChanged(nameof(CurrentEntries));
};
OnPropertyChanging();
OnPropertyChanging(nameof(CurrentEntries));
_onlyShowOwnedGames = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CurrentEntries));
}
}
public void Search(string searchTerm)

View File

@@ -4,7 +4,7 @@ namespace Ryujinx.Ava.Utilities
{
public static class TitleHelper
{
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "")
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, bool customTitlebar, string pauseString = "")
{
if (activeProcess == null)
return string.Empty;
@@ -14,7 +14,9 @@ namespace Ryujinx.Ava.Utilities
string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})";
string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)";
string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
string appTitle = customTitlebar
? $"Ryujinx {applicationVersion}\n{titleNameSection.Trim()}\n{titleVersionSection.Trim()}\n{titleIdSection.Trim()}{titleArchSection}"
: $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
return !string.IsNullOrEmpty(pauseString)
? appTitle + $" ({pauseString})"