Compare commits

...

13 Commits

Author SHA1 Message Date
LotP1
9ba2b43fa5 Merge 176ce064e5 into 3f12727ef8 2025-01-26 06:31:16 +01:00
Evan Husted
3f12727ef8 input: LED rainbow now updates the LED with the normal gamepad update loop instead of subscribing to an updated event for the rainbow color in SetConfiguration. 2025-01-25 23:13:51 -06:00
Evan Husted
8bfcebebf1 Merge remote-tracking branch 'origin/master' 2025-01-25 22:44:26 -06:00
Evan Husted
e3f20abd23 UI: RPC: Maintain game started timestamp for the duration of the AppHost 2025-01-25 22:44:16 -06:00
LotP1
176ce064e5 Update locales.json 2025-01-25 22:02:43 -06:00
LotP1
09b668f468 default value of dishCacheSelector should be null 2025-01-25 22:02:43 -06:00
LotP1
1f7d5a8499 Add nuke PPTC option to cache management 2025-01-25 22:02:43 -06:00
LotP1
02ccb359c4 Remove outdated comment 2025-01-25 22:02:43 -06:00
LotP1
13e081ebe5 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
2025-01-25 22:02:43 -06:00
LotP1
70318fc55a Fix incorrect hash logic
The stream hadn't been reset causing all hashes to be the same in most cases
2025-01-25 22:02:43 -06:00
LotP1
30d6e19397 Add cacheselector and allow PPTC with exefs mods
this is currently broken with Exlaunch mods that use hooks
2025-01-25 22:02:43 -06:00
Daenorth
0c36bcd7d4 Added more titles to RPC (#569)
Added some more titles to the RPC environment

-Brawlhalla
-Minecraft
-Risk
-Stardew Vallet
-Valkyria Chronicles 4
-Super bomberman R
-Arcade archives Super mario bros
-Divinity Original sin 2 DE
-Monopoly
-titan Quest
2025-01-25 22:02:24 -06:00
shinyoyo
050b9a0da4 Updated Zh_CN Simplified Chinese (#578) 2025-01-25 21:59:20 -06:00
18 changed files with 354 additions and 68 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;
@@ -30,8 +31,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 +185,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";
@@ -532,7 +563,7 @@ namespace ARMeilleure.Translation.PTC
public void LoadTranslations(Translator translator)
{
if (AreCarriersEmpty())
if (AreCarriersEmpty() || ContainsBlacklistedFunctions())
{
return;
}
@@ -835,10 +866,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);
@@ -885,7 +924,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 (KeyValuePair<ulong, FuncProfile> 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

@@ -105,6 +105,7 @@ namespace Ryujinx.Common
"0100b04011742000", // Monster Hunter Rise
//Mario Franchise
"010021d00812a000", // Arcade Archives VS. SUPER MARIO BROS.
"01006d0017f7a000", // Mario & Luigi: Brothership
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"010067300059a000", // Mario + Rabbids: Kingdom Battle
@@ -212,32 +213,41 @@ namespace Ryujinx.Common
//Misc Games
"010056e00853a000", // A Hat in Time
"0100fd1014726000", // Baldurs Gate: Dark Alliance
"0100c6800b934000", // Brawlhalla
"0100dbf01000a000", // Burnout Paradise Remastered
"0100744001588000", // Cars 3: Driven to Win
"0100b41013c82000", // Cruis'n Blast
"010085900337e000", // Death Squared
"01001b300b9be000", // Diablo III: Eternal Collection
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
"01008c8012920000", // Dying Light Platinum Edition
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"010085500130a000", // Lego City: Undercover
"010073c01af34000", // LEGO Horizon Adventures
"0100d71004694000", // Minecraft
"01007430037f6000", // Monopoly
"0100853015e86000", // No Man's Sky
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01008e200c5c2000", // Muse Dash
"01007820196a6000", // Red Dead Redemption
"0100e8300a67a000", // Risk
"01002f7013224000", // Rune Factory 5
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"01001180021fa000", // Shovel Knight: Specter of Torment
"0100e65002bb8000", // Stardew Valley
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
"0100800015926000", // Suika Game
"01007ad00013e000", // Super Bomberman R
"0100e46006708000", // Terraria
"0100605008268000", // Titan Quest
"01000a10041ea000", // The Elder Scrolls V: Skyrim
"010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon-
"010080b00ad66000", // Undertale
"010069401adb8000", // Unicorn Overlord
"01005c600ac68000", // Valkyria Chronicles 4
"0100534009ff2000", // Yonder - The cloud catcher chronicles
];
}

View File

@@ -1,6 +1,7 @@
using Gommon;
using System;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.Common.Utilities
@@ -18,7 +19,7 @@ namespace Ryujinx.Common.Utilities
{
while (CyclingEnabled)
{
await Task.Delay(15);
await Task.Delay(20);
Tick();
}
});
@@ -32,29 +33,47 @@ namespace Ryujinx.Common.Utilities
public static float Speed { get; set; } = 1;
private static readonly Lock _lock = new();
public static Color Color { get; private set; } = Color.Blue;
private static Color _color = Color.Blue;
public static ref Color Color
{
get
{
lock (_lock)
{
return ref _color;
}
}
}
public static void Tick()
{
Color = HsbToRgb((Color.GetHue() + Speed) / 360);
lock (_lock)
{
_color = HsbToRgb((_color.GetHue() + Speed) / 360);
UpdatedHandler.Call(Color.ToArgb());
_updatedHandler.Call(_color.ToArgb());
}
}
public static void Reset()
{
Color = Color.Blue;
UpdatedHandler.Clear();
_updatedHandler.Clear();
lock (_lock)
_color = Color.Blue;
}
public static event Action<int> Updated
{
add => UpdatedHandler.Add(value);
remove => UpdatedHandler.Remove(value);
add => _updatedHandler.Add(value);
remove => _updatedHandler.Remove(value);
}
internal static Event<int> UpdatedHandler = new();
private static readonly Event<int> _updatedHandler = new();
private static Color HsbToRgb(float hue, float saturation = 1, float brightness = 1)
{

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

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

@@ -148,8 +148,6 @@ namespace Ryujinx.Input.SDL2
{
if (disposing && _gamepadHandle != nint.Zero)
{
Rainbow.Updated -= RainbowColorChanged;
SDL_GameControllerClose(_gamepadHandle);
_gamepadHandle = nint.Zero;
@@ -227,15 +225,6 @@ namespace Ryujinx.Input.SDL2
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
private void RainbowColorChanged(int packedRgb)
{
if (!_configuration.Led.UseRainbow) return;
SetLed((uint)packedRgb);
}
private bool _rainbowColorEnabled;
public void SetConfiguration(InputConfig configuration)
{
@@ -247,19 +236,10 @@ namespace Ryujinx.Input.SDL2
{
if (_configuration.Led.TurnOffLed)
(this as IGamepad).ClearLed();
else switch (_configuration.Led.UseRainbow)
{
case true when !_rainbowColorEnabled:
Rainbow.Updated += RainbowColorChanged;
_rainbowColorEnabled = true;
break;
case false when _rainbowColorEnabled:
Rainbow.Updated -= RainbowColorChanged;
_rainbowColorEnabled = false;
break;
}
else if (_configuration.Led.UseRainbow)
SetLed((uint)Rainbow.Color.ToArgb());
if (!_configuration.Led.TurnOffLed && !_rainbowColorEnabled)
if (!_configuration.Led.TurnOffLed && !_configuration.Led.UseRainbow)
SetLed(_configuration.Led.LedColor);
}

View File

@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Threading;
using DiscordRPC;
using LibHac.Common;
using LibHac.Ns;
using LibHac.Tools.FsSystem;
@@ -594,6 +595,8 @@ namespace Ryujinx.Ava
gamepad?.ClearLed();
gamepad?.Dispose();
}
DiscordIntegrationModule.GuestAppStartedAt = null;
Rainbow.Disable();
Rainbow.Reset();
@@ -685,6 +688,8 @@ namespace Ryujinx.Ava
public async Task<bool> LoadGuestApplication(BlitStruct<ApplicationControlProperty>? customNacpData = null)
{
DiscordIntegrationModule.GuestAppStartedAt = Timestamps.Now;
InitEmulatedSwitch();
MainWindow.UpdateGraphicsConfig();

View File

@@ -593,7 +593,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "启动游戏时隐藏 UI",
"zh_TW": ""
}
},
@@ -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": {
@@ -2343,7 +2393,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "从选定的 DLC 文件中解压 RomFS",
"zh_TW": ""
}
},
@@ -7668,7 +7718,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "关闭",
"zh_TW": ""
}
},
@@ -7693,7 +7743,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "彩虹",
"zh_TW": ""
}
},
@@ -12922,6 +12972,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": {
@@ -23043,7 +23118,7 @@
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_CN": "选择一个要解压的 DLC",
"zh_TW": ""
}
}

View File

@@ -14,7 +14,8 @@ namespace Ryujinx.Ava
{
public static class DiscordIntegrationModule
{
public static Timestamps StartedAt { get; set; }
public static Timestamps EmulatorStartedAt { get; set; }
public static Timestamps GuestAppStartedAt { get; set; }
private static string VersionString
=> (ReleaseInformation.IsCanaryBuild ? "Canary " : string.Empty) + $"v{ReleaseInformation.Version}";
@@ -43,7 +44,7 @@ namespace Ryujinx.Ava
},
Details = "Main Menu",
State = "Idling",
Timestamps = StartedAt
Timestamps = EmulatorStartedAt
};
ConfigurationState.Instance.EnableDiscordIntegration.Event += Update;
@@ -100,7 +101,7 @@ namespace Ryujinx.Ava
State = appMeta.LastPlayed.HasValue && appMeta.TimePlayed.TotalSeconds > 5
? $"Total play time: {ValueFormatUtils.FormatTimeSpan(appMeta.TimePlayed)}"
: "Never played",
Timestamps = Timestamps.Now
Timestamps = GuestAppStartedAt ??= Timestamps.Now
});
}

View File

@@ -33,7 +33,7 @@ namespace Ryujinx.Headless
public static void Initialize()
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
DiscordIntegrationModule.StartedAt = Timestamps.Now;
DiscordIntegrationModule.EmulatorStartedAt = Timestamps.Now;
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);

View File

@@ -94,7 +94,7 @@ namespace Ryujinx.Ava
private static void Initialize(string[] args)
{
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
DiscordIntegrationModule.StartedAt = Timestamps.Now;
DiscordIntegrationModule.EmulatorStartedAt = Timestamps.Now;
// Parse arguments
CommandLineState.ParseArguments(args);

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

@@ -175,6 +175,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 })