Compare commits

...

15 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
Dehunc
a1291f1061 Improved Simplified Chinese translation (#568)
Co-authored-by: Cwood <Cwood@DoVirt.org>
2025-01-25 21:59:06 -06:00
Otozinclus
f15aa8fba0 Fix LED turning on in input settings, despite TurnOffLed being set to true (#583)
The ColorPicker auotmatically sets the LED to the selected Color
whenever the Input Settings are opened. Therefore it now checks if the
setting is turned off before changing the color.
2025-01-25 17:15:17 -06:00
19 changed files with 401 additions and 113 deletions

View File

@@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Linking;
using ARMeilleure.CodeGen.Unwinding; using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Common; using ARMeilleure.Common;
using ARMeilleure.Memory; using ARMeilleure.Memory;
using ARMeilleure.State;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -31,7 +32,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0"; private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\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 ActualDir = "0";
private const string BackupDir = "1"; private const string BackupDir = "1";
@@ -184,6 +185,36 @@ namespace ARMeilleure.Translation.PTC
InitializeCarriers(); 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() private void PreLoad()
{ {
string fileNameActual = $"{CachePathActual}.cache"; string fileNameActual = $"{CachePathActual}.cache";
@@ -532,7 +563,7 @@ namespace ARMeilleure.Translation.PTC
public void LoadTranslations(Translator translator) public void LoadTranslations(Translator translator)
{ {
if (AreCarriersEmpty()) if (AreCarriersEmpty() || ContainsBlacklistedFunctions())
{ {
return; return;
} }
@@ -835,10 +866,18 @@ namespace ARMeilleure.Translation.PTC
while (profiledFuncsToTranslate.TryDequeue(out (ulong address, PtcProfiler.FuncProfile funcProfile) item)) while (profiledFuncsToTranslate.TryDequeue(out (ulong address, PtcProfiler.FuncProfile funcProfile) item))
{ {
ulong address = item.address; ulong address = item.address;
ExecutionMode executionMode = item.funcProfile.Mode;
bool highCq = item.funcProfile.HighCq;
Debug.Assert(Profiler.IsAddressInStaticCodeRange(address)); 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); bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func);
@@ -885,7 +924,14 @@ namespace ARMeilleure.Translation.PTC
PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount); PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount);
if (_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 | 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) 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 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. private const int SaveInterval = 30; // Seconds.
@@ -76,20 +77,30 @@ namespace ARMeilleure.Translation.PTC
private void TimerElapsed(object _, ElapsedEventArgs __) private void TimerElapsed(object _, ElapsedEventArgs __)
=> new Thread(PreSave) { Name = "Ptc.DiskWriter" }.Start(); => 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)) if (IsAddressInStaticCodeRange(address))
{ {
Debug.Assert(!highCq); Debug.Assert(!highCq);
if (blacklist)
{
lock (_lock) lock (_lock)
{ {
ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false)); 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)) if (IsAddressInStaticCodeRange(address))
{ {
@@ -99,7 +110,7 @@ namespace ARMeilleure.Translation.PTC
{ {
Debug.Assert(ProfiledFuncs.ContainsKey(address)); 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) 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)); profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value));
} }
@@ -130,6 +141,24 @@ namespace ARMeilleure.Translation.PTC
ProfiledFuncs.TrimExcess(); 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() public void PreLoad()
{ {
_lastHash = default; _lastHash = default;
@@ -220,13 +249,18 @@ namespace ARMeilleure.Translation.PTC
return false; return false;
} }
Func<ulong, FuncProfile, (ulong, FuncProfile)> migrateEntryFunc = null;
switch (outerHeader.InfoFileVersion) switch (outerHeader.InfoFileVersion)
{ {
case InternalVersion: case InternalVersion:
ProfiledFuncs = Deserialize(stream); ProfiledFuncs = Deserialize(stream);
break; break;
case 1866: 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; break;
default: default:
Logger.Error?.Print(LogClass.Ptc, $"No migration path for {nameof(outerHeader.InfoFileVersion)} '{outerHeader.InfoFileVersion}'. Discarding cache."); 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>); 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) private static ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
{ {
return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position); 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 struct FuncProfile
{ {
public ExecutionMode Mode; public ExecutionMode Mode;
public bool HighCq; 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; Mode = mode;
HighCq = highCq; HighCq = highCq;

View File

@@ -249,6 +249,11 @@ namespace ARMeilleure.Translation
ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter); ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter<uint> counter);
if (cfg == null)
{
return null;
}
ulong funcSize = funcRange.End - funcRange.Start; ulong funcSize = funcRange.End - funcRange.Start;
Logger.EndPass(PassName.Translation, cfg); Logger.EndPass(PassName.Translation, cfg);
@@ -407,6 +412,11 @@ namespace ARMeilleure.Translation
if (opCode.Instruction.Emitter != null) if (opCode.Instruction.Emitter != null)
{ {
opCode.Instruction.Emitter(context); opCode.Instruction.Emitter(context);
if (opCode.Instruction.Name == InstName.Und && blkIndex == 0)
{
range = new Range(rangeStart, rangeEnd);
return null;
}
} }
else else
{ {

View File

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

View File

@@ -1,6 +1,7 @@
using Gommon; using Gommon;
using System; using System;
using System.Drawing; using System.Drawing;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Common.Utilities namespace Ryujinx.Common.Utilities
@@ -18,7 +19,7 @@ namespace Ryujinx.Common.Utilities
{ {
while (CyclingEnabled) while (CyclingEnabled)
{ {
await Task.Delay(15); await Task.Delay(20);
Tick(); Tick();
} }
}); });
@@ -33,28 +34,46 @@ namespace Ryujinx.Common.Utilities
public static float Speed { get; set; } = 1; public static float Speed { get; set; } = 1;
public static Color Color { get; private set; } = Color.Blue; private static readonly Lock _lock = new();
private static Color _color = Color.Blue;
public static ref Color Color
{
get
{
lock (_lock)
{
return ref _color;
}
}
}
public static void Tick() 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() public static void Reset()
{ {
Color = Color.Blue; _updatedHandler.Clear();
UpdatedHandler.Clear();
lock (_lock)
_color = Color.Blue;
} }
public static event Action<int> Updated public static event Action<int> Updated
{ {
add => UpdatedHandler.Add(value); add => _updatedHandler.Add(value);
remove => UpdatedHandler.Remove(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) 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 _titleIdText;
private readonly string _displayVersion; private readonly string _displayVersion;
private readonly bool _diskCacheEnabled; private readonly bool _diskCacheEnabled;
private readonly string _diskCacheSelector;
private readonly ulong _codeAddress; private readonly ulong _codeAddress;
private readonly ulong _codeSize; private readonly ulong _codeSize;
@@ -31,6 +32,7 @@ namespace Ryujinx.HLE.HOS
string titleIdText, string titleIdText,
string displayVersion, string displayVersion,
bool diskCacheEnabled, bool diskCacheEnabled,
string diskCacheSelector,
ulong codeAddress, ulong codeAddress,
ulong codeSize) ulong codeSize)
{ {
@@ -39,6 +41,7 @@ namespace Ryujinx.HLE.HOS
_titleIdText = titleIdText; _titleIdText = titleIdText;
_displayVersion = displayVersion; _displayVersion = displayVersion;
_diskCacheEnabled = diskCacheEnabled; _diskCacheEnabled = diskCacheEnabled;
_diskCacheSelector = diskCacheSelector;
_codeAddress = codeAddress; _codeAddress = codeAddress;
_codeSize = codeSize; _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; return processContext;
} }

View File

@@ -6,6 +6,7 @@ using LibHac.Loader;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.RomFs; using LibHac.Tools.FsSystem.RomFs;
using LibHac.Util;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
@@ -19,6 +20,7 @@ using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using LazyFile = Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.LazyFile; using LazyFile = Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.LazyFile;
using Path = System.IO.Path; using Path = System.IO.Path;
@@ -581,6 +583,7 @@ namespace Ryujinx.HLE.HOS
public BitVector32 Stubs; public BitVector32 Stubs;
public BitVector32 Replaces; public BitVector32 Replaces;
public MetaLoader Npdm; public MetaLoader Npdm;
public string Hash;
public bool Modified => (Stubs.Data | Replaces.Data) != 0; public bool Modified => (Stubs.Data | Replaces.Data) != 0;
} }
@@ -591,8 +594,11 @@ namespace Ryujinx.HLE.HOS
{ {
Stubs = new BitVector32(), Stubs = new BitVector32(),
Replaces = new BitVector32(), Replaces = new BitVector32(),
Hash = null,
}; };
string tempHash = string.Empty;
if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsDirs.Count == 0) if (!_appMods.TryGetValue(applicationId, out ModCache mods) || mods.ExefsDirs.Count == 0)
{ {
return modLoadResult; return modLoadResult;
@@ -628,8 +634,16 @@ namespace Ryujinx.HLE.HOS
modLoadResult.Replaces[1 << i] = true; modLoadResult.Replaces[1 << i] = true;
nsos[i] = new NsoExecutable(nsoFile.OpenRead().AsStorage(), nsoName); using (FileStream stream = nsoFile.OpenRead())
{
nsos[i] = new NsoExecutable(stream.AsStorage(), nsoName);
Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced"); 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)); 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; return modLoadResult;
} }

View File

@@ -84,13 +84,6 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
// Apply Nsos patches. // Apply Nsos patches.
device.Configuration.VirtualFileSystem.ModLoader.ApplyNsoPatches(programId, nsoExecutables); 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; string programName = string.Empty;
if (!isHomebrew && programId > 0x010000000000FFFF) if (!isHomebrew && programId > 0x010000000000FFFF)
@@ -117,7 +110,8 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
device.System.KernelContext, device.System.KernelContext,
metaLoader, metaLoader,
nacpData, nacpData,
enablePtc, device.System.EnablePtc,
modLoadResult.Hash,
true, true,
programName, programName,
metaLoader.GetProgramId(), metaLoader.GetProgramId(),

View File

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

View File

@@ -186,6 +186,7 @@ namespace Ryujinx.HLE.Loaders.Processes
string.Empty, string.Empty,
string.Empty, string.Empty,
false, false,
null,
codeAddress, codeAddress,
codeSize); codeSize);
@@ -226,6 +227,7 @@ namespace Ryujinx.HLE.Loaders.Processes
MetaLoader metaLoader, MetaLoader metaLoader,
BlitStruct<ApplicationControlProperty> applicationControlProperties, BlitStruct<ApplicationControlProperty> applicationControlProperties,
bool diskCacheEnabled, bool diskCacheEnabled,
string diskCacheSelector,
bool allowCodeMemoryForJit, bool allowCodeMemoryForJit,
string name, string name,
ulong programId, ulong programId,
@@ -379,6 +381,7 @@ namespace Ryujinx.HLE.Loaders.Processes
$"{programId:x16}", $"{programId:x16}",
displayVersion, displayVersion,
diskCacheEnabled, diskCacheEnabled,
diskCacheSelector,
codeStart, codeStart,
codeSize); codeSize);

View File

@@ -148,8 +148,6 @@ namespace Ryujinx.Input.SDL2
{ {
if (disposing && _gamepadHandle != nint.Zero) if (disposing && _gamepadHandle != nint.Zero)
{ {
Rainbow.Updated -= RainbowColorChanged;
SDL_GameControllerClose(_gamepadHandle); SDL_GameControllerClose(_gamepadHandle);
_gamepadHandle = nint.Zero; _gamepadHandle = nint.Zero;
@@ -228,15 +226,6 @@ namespace Ryujinx.Input.SDL2
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY; 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) public void SetConfiguration(InputConfig configuration)
{ {
lock (_userMappingLock) lock (_userMappingLock)
@@ -247,19 +236,10 @@ namespace Ryujinx.Input.SDL2
{ {
if (_configuration.Led.TurnOffLed) if (_configuration.Led.TurnOffLed)
(this as IGamepad).ClearLed(); (this as IGamepad).ClearLed();
else switch (_configuration.Led.UseRainbow) else if (_configuration.Led.UseRainbow)
{ SetLed((uint)Rainbow.Color.ToArgb());
case true when !_rainbowColorEnabled:
Rainbow.Updated += RainbowColorChanged;
_rainbowColorEnabled = true;
break;
case false when _rainbowColorEnabled:
Rainbow.Updated -= RainbowColorChanged;
_rainbowColorEnabled = false;
break;
}
if (!_configuration.Led.TurnOffLed && !_rainbowColorEnabled) if (!_configuration.Led.TurnOffLed && !_configuration.Led.UseRainbow)
SetLed(_configuration.Led.LedColor); SetLed(_configuration.Led.LedColor);
} }

View File

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

View File

@@ -593,7 +593,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "", "zh_CN": "启动游戏时隐藏 UI",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -768,7 +768,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Сканувати Amiibo (з теки Bin)", "uk_UA": "Сканувати Amiibo (з теки Bin)",
"zh_CN": "从bin文件扫描 Amiibo", "zh_CN": "扫描 Amiibo (从 bin 文件)",
"zh_TW": "掃瞄 Amiibo (從 Bin 檔案)" "zh_TW": "掃瞄 Amiibo (從 Bin 檔案)"
} }
}, },
@@ -818,7 +818,7 @@
"th_TH": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP", "th_TH": "ติดตั้งเฟิร์มแวร์จาก ไฟล์ XCI หรือ ไฟล์ ZIP",
"tr_TR": "XCI veya ZIP'ten Yazılım Yükle", "tr_TR": "XCI veya ZIP'ten Yazılım Yükle",
"uk_UA": "Встановити прошивку з XCI або ZIP", "uk_UA": "Встановити прошивку з XCI або ZIP",
"zh_CN": "从 XCI 或 ZIP 文件安装系统固件", "zh_CN": "从 XCI 或 ZIP 文件安装系统固件",
"zh_TW": "從 XCI 或 ZIP 安裝韌體" "zh_TW": "從 XCI 或 ZIP 安裝韌體"
} }
}, },
@@ -843,7 +843,7 @@
"th_TH": "ติดตั้งเฟิร์มแวร์จากไดเร็กทอรี", "th_TH": "ติดตั้งเฟิร์มแวร์จากไดเร็กทอรี",
"tr_TR": "Bir Dizin Üzerinden Yazılım Yükle", "tr_TR": "Bir Dizin Üzerinden Yazılım Yükle",
"uk_UA": "Встановити прошивку з теки", "uk_UA": "Встановити прошивку з теки",
"zh_CN": "从文件夹安装系统固件", "zh_CN": "从文件夹安装系统固件",
"zh_TW": "從資料夾安裝韌體" "zh_TW": "從資料夾安裝韌體"
} }
}, },
@@ -893,7 +893,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Встановити ключі з файлу .KEYS або .ZIP", "uk_UA": "Встановити ключі з файлу .KEYS або .ZIP",
"zh_CN": "从.KEYS文件或ZIP压缩包安装密匙", "zh_CN": "从 .KEYS 文件或 .ZIP 压缩包安装密匙",
"zh_TW": "從 .KEYS 或 .ZIP 安裝金鑰" "zh_TW": "從 .KEYS 或 .ZIP 安裝金鑰"
} }
}, },
@@ -1018,7 +1018,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Обрізати XCI файли", "uk_UA": "Обрізати XCI файли",
"zh_CN": "XCI文件瘦身", "zh_CN": "瘦身 XCI 文件",
"zh_TW": "修剪 XCI 檔案" "zh_TW": "修剪 XCI 檔案"
} }
}, },
@@ -1293,7 +1293,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Відкриває сторінку з Посібником по усуненню помилок та несправностей на офіційній вікі-сторінці Ryujinx (англійською)", "uk_UA": "Відкриває сторінку з Посібником по усуненню помилок та несправностей на офіційній вікі-сторінці Ryujinx (англійською)",
"zh_CN": "打开Ryujinx官方wiki的常见问题和问题排除页面", "zh_CN": "打开 Ryujinx 官方 Wiki 的常见问题和问题排除页面",
"zh_TW": "開啟官方 Ryujinx Wiki 常見問題 (FAQ) 和疑難排解頁面" "zh_TW": "開啟官方 Ryujinx Wiki 常見問題 (FAQ) 和疑難排解頁面"
} }
}, },
@@ -1343,7 +1343,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Відкриває посібник з Налаштування та конфігурації на офіційній вікі-сторінці Ryujinx (англійською)", "uk_UA": "Відкриває посібник з Налаштування та конфігурації на офіційній вікі-сторінці Ryujinx (англійською)",
"zh_CN": "打开Ryujinx官方wiki的安装与配置指南", "zh_CN": "打开 Ryujinx 官方 Wiki 的安装与配置指南",
"zh_TW": "開啟官方 Ryujinx Wiki 設定和配置指南" "zh_TW": "開啟官方 Ryujinx Wiki 設定和配置指南"
} }
}, },
@@ -1393,7 +1393,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Відкриває посібник з налаштування Мультиплеєру на офіційній вікі-сторінці Ryujinx (англійською)", "uk_UA": "Відкриває посібник з налаштування Мультиплеєру на офіційній вікі-сторінці Ryujinx (англійською)",
"zh_CN": "打开Ryujinx官方wiki的多人游戏指南", "zh_CN": "打开 Ryujinx 官方 Wiki 的多人游戏指南",
"zh_TW": "開啟官方 Ryujinx Wiki 多人遊戲 (LDN/LAN) 指南" "zh_TW": "開啟官方 Ryujinx Wiki 多人遊戲 (LDN/LAN) 指南"
} }
}, },
@@ -1443,7 +1443,7 @@
"th_TH": "กำลังค้นหา...", "th_TH": "กำลังค้นหา...",
"tr_TR": "Ara...", "tr_TR": "Ara...",
"uk_UA": "Пошук...", "uk_UA": "Пошук...",
"zh_CN": "搜索", "zh_CN": "搜索...",
"zh_TW": "搜尋..." "zh_TW": "搜尋..."
} }
}, },
@@ -2022,6 +2022,56 @@
"zh_TW": "下一次啟動遊戲時,觸發 PPTC 進行重建" "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", "ID": "GameListContextMenuCacheManagementPurgeShaderCache",
"Translations": { "Translations": {
@@ -2343,7 +2393,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "", "zh_CN": "从选定的 DLC 文件中解压 RomFS",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -2693,7 +2743,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Обрізається XCI Файлів '{0}'", "uk_UA": "Обрізається XCI Файлів '{0}'",
"zh_CN": "XCI文件瘦身中'{0}'", "zh_CN": "正在瘦身 XCI 文件 '{0}'",
"zh_TW": "正在修剪 XCI 檔案 '{0}'" "zh_TW": "正在修剪 XCI 檔案 '{0}'"
} }
}, },
@@ -3218,7 +3268,7 @@
"th_TH": "โหลดไดเรกทอรี DLC/ไฟล์อัปเดต อัตโนมัติ", "th_TH": "โหลดไดเรกทอรี DLC/ไฟล์อัปเดต อัตโนมัติ",
"tr_TR": "", "tr_TR": "",
"uk_UA": "Автозавантаження теки DLC/Оновлень", "uk_UA": "Автозавантаження теки DLC/Оновлень",
"zh_CN": "自动加载DLC/游戏更新目录", "zh_CN": "自动加载 DLC游戏更新目录",
"zh_TW": "自動載入 DLC/遊戲更新資料夾" "zh_TW": "自動載入 DLC/遊戲更新資料夾"
} }
}, },
@@ -3243,7 +3293,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "DLC та Оновлення, які посилаються на відсутні файли, будуть автоматично вимкнуті.", "uk_UA": "DLC та Оновлення, які посилаються на відсутні файли, будуть автоматично вимкнуті.",
"zh_CN": "DLC/游戏更新可自动加载和卸载", "zh_CN": "DLC游戏更新 可自动加载和卸载",
"zh_TW": "遺失的 DLC 及遊戲更新檔案將會在自動載入中移除" "zh_TW": "遺失的 DLC 及遊戲更新檔案將會在自動載入中移除"
} }
}, },
@@ -7668,7 +7718,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "", "zh_CN": "关闭",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -7693,7 +7743,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "", "zh_CN": "彩虹",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -12922,6 +12972,31 @@
"zh_TW": "在 {0} 清除 PPTC 快取時出錯: {1}" "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", "ID": "DialogShaderDeletionMessage",
"Translations": { "Translations": {
@@ -23043,7 +23118,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "", "zh_CN": "选择一个要解压的 DLC",
"zh_TW": "" "zh_TW": ""
} }
} }

View File

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

View File

@@ -33,7 +33,7 @@ namespace Ryujinx.Headless
public static void Initialize() public static void Initialize()
{ {
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched // 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. // Delete backup files after updating.
Task.Run(Updater.CleanupUpdate); Task.Run(Updater.CleanupUpdate);

View File

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

View File

@@ -81,6 +81,11 @@
Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}" Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}"
Icon="{ext:Icon mdi-refresh}" Icon="{ext:Icon mdi-refresh}"
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> 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 <MenuItem
Click="PurgeShaderCache_Click" Click="PurgeShaderCache_Click"
Header="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCache}" 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) public async void PurgeShaderCache_Click(object sender, RoutedEventArgs args)
{ {
if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) if (sender is not MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

View File

@@ -252,6 +252,7 @@ namespace Ryujinx.Ava.UI.Views.Input
if (!args.NewColor.HasValue) return; if (!args.NewColor.HasValue) return;
if (DataContext is not ControllerInputViewModel cVm) return; if (DataContext is not ControllerInputViewModel cVm) return;
if (!cVm.Config.EnableLedChanging) return; if (!cVm.Config.EnableLedChanging) return;
if (cVm.Config.TurnOffLed) return;
cVm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32()); cVm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
} }
@@ -260,6 +261,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
if (DataContext is not ControllerInputViewModel cVm) return; if (DataContext is not ControllerInputViewModel cVm) return;
if (!cVm.Config.EnableLedChanging) return; if (!cVm.Config.EnableLedChanging) return;
if (cVm.Config.TurnOffLed) return;
cVm.ParentModel.SelectedGamepad.SetLed(cVm.Config.LedColor.ToUInt32()); cVm.ParentModel.SelectedGamepad.SetLed(cVm.Config.LedColor.ToUInt32());
} }