Compare commits

...

19 Commits

Author SHA1 Message Date
LotP1 523be83dea Update locales.json 2025-01-25 19:12:41 +01:00
LotP1 8319e95add default value of dishCacheSelector should be null 2025-01-25 19:12:41 +01:00
LotP1 11db70beea Add nuke PPTC option to cache management 2025-01-25 19:12:40 +01:00
LotP1 625e2c52e2 Remove outdated comment 2025-01-25 18:58:08 +01:00
LotP1 2f7332ad51 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 18:58:08 +01:00
LotP1 aa6383359f Fix incorrect hash logic
The stream hadn't been reset causing all hashes to be the same in most cases
2025-01-25 18:56:43 +01:00
LotP1 f3d050b44f Add cacheselector and allow PPTC with exefs mods
this is currently broken with Exlaunch mods that use hooks
2025-01-25 18:56:43 +01:00
Evan Husted 3b5f6170d1 misc: chore: move Rainbow updating to a separate task started/stopped as needed
update gommon & use the Event class from it to allow easily clearing all handlers when the apphost exits to avoid leftover invalid event handlers in the rainbow event handler list.
More robust config application logic to ensure what needs to happen only happens once
2025-01-24 23:06:59 -06:00
Evan Husted 9b6afa0ea2 misc: chore: Add log line to the other parts of the Updater that represent "up to date" 2025-01-24 17:00:50 -06:00
Evan Husted 3541e282ea Fully disconnect gamepad handler for rainbow color if configuration is set with UseRainbowLed false
Also check if its even enabled before setting the rainbow color
Fixes strobing
2025-01-24 16:52:20 -06:00
Otozinclus 1ce37ec317 Add option to change controller LED color (#572)
This allows the user to change the controller LED while using Ryujinx.
Useful for PS4 and PS5 controllers as an example.

You can also use a spectrum-cycling Rainbow color option, or turn the LED off for DualSense controllers.

---------

Co-authored-by: Evan Husted <greem@greemdev.net>
2025-01-24 14:47:36 -06:00
Evan Husted c06f16c5e6 infra: chore: Raise minimum required Windows 10 version
Inspired by the breakages covered in #409
2025-01-23 17:39:34 -06:00
Evan Husted 7829fd8ee7 misc: chore: OS + CPU arch helpers 2025-01-23 16:58:48 -06:00
Evan Husted 33079422fe misc: chore: code cleanups 2025-01-23 16:47:11 -06:00
Evan Husted f81cb093fc misc: chore: Change references of GreemDev/Ryujinx to Ryubing/Ryujinx 2025-01-23 16:27:49 -06:00
Evan Husted dc0c7a2912 infra: clarify how stable builds are made now 2025-01-23 16:27:25 -06:00
Evan Husted 1fbee5a584 infra: Metal label 2025-01-23 14:39:36 -06:00
Evan Husted c140e9b23c UI: Localize LED color & hide it until it's functional
Also moved IgnoreApplet to the System config section object.
2025-01-23 00:48:42 -06:00
Evan Husted 9c8055440e HLE: TryAdd firmware NCAs 2025-01-22 23:58:11 -06:00
55 changed files with 867 additions and 114 deletions
+4
View File
@@ -18,6 +18,10 @@ gpu:
- changed-files: - changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**'] - any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**']
'graphics-backend:metal':
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Metal/**', 'src/Ryujinx.Graphics.Metal.SharpMetalExtensions/**']
gui: gui:
- changed-files: - changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**'] - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**']
+1 -1
View File
@@ -42,7 +42,7 @@
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Gommon" Version="2.7.0.2" /> <PackageVersion Include="Gommon" Version="2.7.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" /> <PackageVersion Include="Sep" Version="0.6.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
+4 -3
View File
@@ -54,12 +54,13 @@ failing to meet this requirement may result in a poor gameplay experience or une
## Latest build ## Latest build
Stable builds are made every so often onto a separate "release" branch that then gets put into the releases you know and love. Stable builds are made every so often, based on the `master` branch, that then gets put into the releases you know and love.
These stable builds exist so that the end user can get a more **enjoyable and stable experience**. These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
You can find the latest stable release [here](https://github.com/GreemDev/Ryujinx/releases/latest). You can find the latest stable release [here](https://github.com/GreemDev/Ryujinx/releases/latest).
Canary builds are compiled automatically for each commit on the master branch. Canary builds are compiled automatically for each commit on the `master` branch.
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**. While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
These canary builds are only recommended for experienced users. These canary builds are only recommended for experienced users.
@@ -109,7 +110,7 @@ If you are planning to contribute or just want to learn more about this project
- **Configuration** - **Configuration**
The emulator has settings for enabling or disabling some logging, remapping controllers, and more. The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI. You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the Ryujinx data folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
## License ## License
+51 -5
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;
@@ -29,8 +30,8 @@ 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";
@@ -183,6 +184,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";
@@ -531,7 +562,7 @@ namespace ARMeilleure.Translation.PTC
public void LoadTranslations(Translator translator) public void LoadTranslations(Translator translator)
{ {
if (AreCarriersEmpty()) if (AreCarriersEmpty() || ContainsBlacklistedFunctions())
{ {
return; return;
} }
@@ -834,10 +865,18 @@ namespace ARMeilleure.Translation.PTC
while (profiledFuncsToTranslate.TryDequeue(out var item)) while (profiledFuncsToTranslate.TryDequeue(out var 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);
@@ -884,7 +923,14 @@ namespace ARMeilleure.Translation.PTC
PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount); 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) Thread preSaveThread = new(PreSave)
{ {
+76 -10
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);
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)) 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 (var profiledFunc in ProfiledFuncs) foreach (var 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;
+10
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
{ {
@@ -2,14 +2,24 @@
{ {
public class LedConfigController public class LedConfigController
{ {
/// <summary>
/// Packed RGB int of the color
/// </summary>
public uint LedColor { get; set; }
/// <summary> /// <summary>
/// Enable LED color changing by the emulator /// Enable LED color changing by the emulator
/// </summary> /// </summary>
public bool EnableLed { get; set; } public bool EnableLed { get; set; }
/// <summary>
/// Ignores the color and disables the LED entirely.
/// </summary>
public bool TurnOffLed { get; set; }
/// <summary>
/// Ignores the color and uses the rainbow color functionality for the LED.
/// </summary>
public bool UseRainbow { get; set; }
/// <summary>
/// Packed RGB int of the color
/// </summary>
public uint LedColor { get; set; }
} }
} }
@@ -0,0 +1,23 @@
using System;
using System.Runtime.InteropServices;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable InconsistentNaming
namespace Ryujinx.Common.Helper
{
public static class RunningPlatform
{
public static bool IsMacOS => OperatingSystem.IsMacOS();
public static bool IsWindows => OperatingSystem.IsWindows();
public static bool IsLinux => OperatingSystem.IsLinux();
public static bool IsIntelMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.X64;
public static bool IsArmMac => IsMacOS && RuntimeInformation.OSArchitecture is Architecture.Arm64;
public static bool IsX64Windows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.X64);
public static bool IsArmWindows => IsWindows && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
public static bool IsX64Linux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.X64);
public static bool IsArmLinux => IsLinux && (RuntimeInformation.OSArchitecture is Architecture.Arm64);
}
}
+3 -2
View File
@@ -1,5 +1,6 @@
using Gommon; using Gommon;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Helper;
using System; using System;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -8,7 +9,7 @@ namespace Ryujinx.Common
{ {
public static class TitleIDs public static class TitleIDs
{ {
public static ReactiveObject<Optional<string>> CurrentApplication { get; set; } = new(); public static ReactiveObject<Optional<string>> CurrentApplication { get; } = new();
public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend) public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
{ {
@@ -21,7 +22,7 @@ namespace Ryujinx.Common
return currentBackend; return currentBackend;
} }
if (!(OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture is Architecture.Arm64)) if (!RunningPlatform.IsArmMac)
return GraphicsBackend.Vulkan; return GraphicsBackend.Vulkan;
return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan; return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan;
+110
View File
@@ -0,0 +1,110 @@
using Gommon;
using System;
using System.Drawing;
using System.Threading.Tasks;
namespace Ryujinx.Common.Utilities
{
public static class Rainbow
{
public static bool CyclingEnabled { get; set; }
public static void Enable()
{
if (!CyclingEnabled)
{
CyclingEnabled = true;
Executor.ExecuteBackgroundAsync(async () =>
{
while (CyclingEnabled)
{
await Task.Delay(15);
Tick();
}
});
}
}
public static void Disable()
{
CyclingEnabled = false;
}
public static float Speed { get; set; } = 1;
public static Color Color { get; private set; } = Color.Blue;
public static void Tick()
{
Color = HsbToRgb((Color.GetHue() + Speed) / 360);
UpdatedHandler.Call(Color.ToArgb());
}
public static void Reset()
{
Color = Color.Blue;
UpdatedHandler.Clear();
}
public static event Action<int> Updated
{
add => UpdatedHandler.Add(value);
remove => UpdatedHandler.Remove(value);
}
internal static Event<int> UpdatedHandler = new();
private static Color HsbToRgb(float hue, float saturation = 1, float brightness = 1)
{
int r = 0, g = 0, b = 0;
if (saturation == 0)
{
r = g = b = (int)(brightness * 255.0f + 0.5f);
}
else
{
float h = (hue - (float)Math.Floor(hue)) * 6.0f;
float f = h - (float)Math.Floor(h);
float p = brightness * (1.0f - saturation);
float q = brightness * (1.0f - saturation * f);
float t = brightness * (1.0f - (saturation * (1.0f - f)));
switch ((int)h)
{
case 0:
r = (int)(brightness * 255.0f + 0.5f);
g = (int)(t * 255.0f + 0.5f);
b = (int)(p * 255.0f + 0.5f);
break;
case 1:
r = (int)(q * 255.0f + 0.5f);
g = (int)(brightness * 255.0f + 0.5f);
b = (int)(p * 255.0f + 0.5f);
break;
case 2:
r = (int)(p * 255.0f + 0.5f);
g = (int)(brightness * 255.0f + 0.5f);
b = (int)(t * 255.0f + 0.5f);
break;
case 3:
r = (int)(p * 255.0f + 0.5f);
g = (int)(q * 255.0f + 0.5f);
b = (int)(brightness * 255.0f + 0.5f);
break;
case 4:
r = (int)(t * 255.0f + 0.5f);
g = (int)(p * 255.0f + 0.5f);
b = (int)(brightness * 255.0f + 0.5f);
break;
case 5:
r = (int)(brightness * 255.0f + 0.5f);
g = (int)(p * 255.0f + 0.5f);
b = (int)(q * 255.0f + 0.5f);
break;
}
}
return Color.FromArgb(Convert.ToByte(255), Convert.ToByte(r), Convert.ToByte(g), Convert.ToByte(b));
}
}
}
+2 -4
View File
@@ -710,9 +710,8 @@ namespace Ryujinx.HLE.FileSystem
{ {
updateNcasItem.Add((nca.Header.ContentType, entry.FullName)); updateNcasItem.Add((nca.Header.ContentType, entry.FullName));
} }
else else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{ {
updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>());
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName)); updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullName));
} }
} }
@@ -898,9 +897,8 @@ namespace Ryujinx.HLE.FileSystem
{ {
updateNcasItem.Add((nca.Header.ContentType, entry.FullPath)); updateNcasItem.Add((nca.Header.ContentType, entry.FullPath));
} }
else else if (updateNcas.TryAdd(nca.Header.TitleId, new List<(NcaContentType, string)>()))
{ {
updateNcas.Add(nca.Header.TitleId, new List<(NcaContentType, string)>());
updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath)); updateNcas[nca.Header.TitleId].Add((nca.Header.ContentType, entry.FullPath));
} }
@@ -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;
} }
+24 -2
View File
@@ -5,6 +5,7 @@ using LibHac.FsSystem;
using LibHac.Loader; using LibHac.Loader;
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;
@@ -18,6 +19,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;
@@ -580,6 +582,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;
} }
@@ -590,8 +593,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;
@@ -627,8 +633,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())
Logger.Info?.Print(LogClass.ModLoader, $"NSO '{nsoName}' replaced"); {
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)); 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; return modLoadResult;
} }
@@ -659,7 +659,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
if (string.IsNullOrWhiteSpace(filePath)) if (string.IsNullOrWhiteSpace(filePath))
{ {
throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/GreemDev/Ryujinx#requirements for more information)"); throw new InvalidSystemResourceException("JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryubing/Ryujinx#requirements for more information)");
} }
context.Device.LoadNca(filePath); context.Device.LoadNca(filePath);
@@ -105,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
titleName = "Unknown"; titleName = "Unknown";
} }
throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/GreemDev/Ryujinx#requirements for more information)"); throw new InvalidSystemResourceException($"{titleName} ({fontTitle:x8}) system title not found! This font will not work, provide the system archive to fix this error. (See https://github.com/Ryubing/Ryujinx#requirements for more information)");
} }
} }
else else
@@ -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(),
@@ -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,
@@ -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);
+15
View File
@@ -161,5 +161,20 @@ namespace Ryujinx.HLE
{ {
return 1000 / _frameRate[FrameTypeGame]; return 1000 / _frameRate[FrameTypeGame];
} }
public string FormatGameFrameRate()
{
double frameRate = GetGameFrameRate();
double frameTime = GetGameFrameTime();
return $"{frameRate:00.00} FPS ({frameTime:00.00}ms)";
}
public string FormatFifoPercent()
{
double fifoPercent = GetFifoPercent();
return $"FIFO: {fifoPercent:00.00}%";
}
} }
} }
+2 -2
View File
@@ -34,8 +34,8 @@ namespace Ryujinx.HLE
public int CpuCoresCount = 4; //Switch 1 has 4 cores public int CpuCoresCount = 4; //Switch 1 has 4 cores
public VSyncMode VSyncMode { get; set; } = VSyncMode.Switch; public VSyncMode VSyncMode { get; set; }
public bool CustomVSyncIntervalEnabled { get; set; } = false; public bool CustomVSyncIntervalEnabled { get; set; }
public int CustomVSyncInterval { get; set; } public int CustomVSyncInterval { get; set; }
public long TargetVSyncInterval { get; set; } = 60; public long TargetVSyncInterval { get; set; } = 60;
+47 -4
View File
@@ -1,6 +1,8 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Hid;
using SDL2; using SDL2;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -86,7 +88,7 @@ namespace Ryujinx.Input.SDL2
Id = driverId; Id = driverId;
Features = GetFeaturesFlag(); Features = GetFeaturesFlag();
_triggerThreshold = 0.0f; _triggerThreshold = 0.0f;
// Enable motion tracking // Enable motion tracking
if (Features.HasFlag(GamepadFeaturesFlag.Motion)) if (Features.HasFlag(GamepadFeaturesFlag.Motion))
{ {
@@ -102,6 +104,18 @@ namespace Ryujinx.Input.SDL2
} }
} }
public void SetLed(uint packedRgb)
{
if (!Features.HasFlag(GamepadFeaturesFlag.Led)) return;
byte red = packedRgb > 0 ? (byte)(packedRgb >> 16) : (byte)0;
byte green = packedRgb > 0 ? (byte)(packedRgb >> 8) : (byte)0;
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
Logger.Error?.Print(LogClass.Hid, "LED is not supported on this game controller.");
}
private GamepadFeaturesFlag GetFeaturesFlag() private GamepadFeaturesFlag GetFeaturesFlag()
{ {
GamepadFeaturesFlag result = GamepadFeaturesFlag.None; GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
@@ -112,9 +126,7 @@ namespace Ryujinx.Input.SDL2
result |= GamepadFeaturesFlag.Motion; result |= GamepadFeaturesFlag.Motion;
} }
int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100); if (SDL_GameControllerHasRumble(_gamepadHandle) == SDL_bool.SDL_TRUE)
if (error == 0)
{ {
result |= GamepadFeaturesFlag.Rumble; result |= GamepadFeaturesFlag.Rumble;
} }
@@ -136,6 +148,8 @@ 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;
@@ -214,12 +228,41 @@ 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)
{ {
_configuration = (StandardControllerInputConfig)configuration; _configuration = (StandardControllerInputConfig)configuration;
if (Features.HasFlag(GamepadFeaturesFlag.Led) && _configuration.Led.EnableLed)
{
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;
}
if (!_configuration.Led.TurnOffLed && !_rainbowColorEnabled)
SetLed(_configuration.Led.LedColor);
}
_buttonsUserMapping.Clear(); _buttonsUserMapping.Clear();
// First update sticks // First update sticks
@@ -173,5 +173,16 @@ namespace Ryujinx.Input.SDL2
return new SDL2Gamepad(gamepadHandle, id); return new SDL2Gamepad(gamepadHandle, id);
} }
public IEnumerable<IGamepad> GetGamepads()
{
lock (_gamepadsIds)
{
foreach (string gamepadId in _gamepadsIds)
{
yield return GetGamepad(gamepadId);
}
}
}
} }
} }
+6
View File
@@ -1,5 +1,6 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
@@ -385,6 +386,11 @@ namespace Ryujinx.Input.SDL2
} }
} }
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Keyboard");
}
public void SetTriggerThreshold(float triggerThreshold) public void SetTriggerThreshold(float triggerThreshold)
{ {
// No operations // No operations
+6
View File
@@ -1,4 +1,5 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using System; using System;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
@@ -76,6 +77,11 @@ namespace Ryujinx.Input.SDL2
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Mouse");
}
public void SetTriggerThreshold(float triggerThreshold) public void SetTriggerThreshold(float triggerThreshold)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -1,6 +1,7 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Numerics; using System.Numerics;
@@ -164,6 +165,8 @@ namespace Ryujinx.Input.SDL2
return new SDL2Mouse(this); return new SDL2Mouse(this);
} }
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
public void Dispose() public void Dispose()
{ {
if (_isDisposed) if (_isDisposed)
@@ -1,5 +1,6 @@
using Ryujinx.SDL2.Common; using Ryujinx.SDL2.Common;
using System; using System;
using System.Collections.Generic;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL2
{ {
@@ -51,5 +52,13 @@ namespace Ryujinx.Input.SDL2
return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards"); return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards");
} }
public IEnumerable<IGamepad> GetGamepads()
{
foreach (var keyboardId in _keyboardIdentifers)
{
yield return GetGamepad(keyboardId);
}
}
} }
} }
+9
View File
@@ -65,6 +65,15 @@ namespace Ryujinx.Input
/// <param name="configuration">The configuration of the gamepad</param> /// <param name="configuration">The configuration of the gamepad</param>
void SetConfiguration(InputConfig configuration); void SetConfiguration(InputConfig configuration);
/// <summary>
/// Set the LED on the gamepad to a given color.
/// </summary>
/// <remarks>Does nothing on a controller without LED functionality.</remarks>
/// <param name="packedRgb">The packed RGB integer.</param>
void SetLed(uint packedRgb);
public void ClearLed() => SetLed(0);
/// <summary> /// <summary>
/// Starts a rumble effect on the gamepad. /// Starts a rumble effect on the gamepad.
/// </summary> /// </summary>
+6
View File
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace Ryujinx.Input namespace Ryujinx.Input
{ {
@@ -33,6 +34,11 @@ namespace Ryujinx.Input
/// <param name="id">The unique id of the gamepad</param> /// <param name="id">The unique id of the gamepad</param>
/// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns> /// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns>
IGamepad GetGamepad(string id); IGamepad GetGamepad(string id);
/// <summary>
/// Returns an <see cref="IEnumerable{T}"/> of the connected gamepads.
/// </summary>
IEnumerable<IGamepad> GetGamepads();
/// <summary> /// <summary>
/// Clear the internal state of the driver. /// Clear the internal state of the driver.
+1
View File
@@ -1,5 +1,6 @@
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
+13 -2
View File
@@ -501,6 +501,8 @@ namespace Ryujinx.Ava
_renderingThread.Start(); _renderingThread.Start();
_viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
Rainbow.Enable();
MainLoop(); MainLoop();
@@ -587,6 +589,15 @@ namespace Ryujinx.Ava
return; return;
} }
foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
{
gamepad?.ClearLed();
gamepad?.Dispose();
}
Rainbow.Disable();
Rainbow.Reset();
_isStopped = true; _isStopped = true;
Stop(); Stop();
} }
@@ -1151,8 +1162,8 @@ namespace Ryujinx.Ava
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
dockedMode, dockedMode,
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
$"{Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", Device.Statistics.FormatGameFrameRate(),
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", Device.Statistics.FormatFifoPercent(),
_displayCount)); _displayCount));
} }
+150
View File
@@ -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": {
@@ -7622,6 +7672,81 @@
"zh_TW": "陀螺儀無感帶:" "zh_TW": "陀螺儀無感帶:"
} }
}, },
{
"ID": "ControllerSettingsLedColor",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "LED",
"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": "ControllerSettingsLedColorDisable",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Disable",
"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": "ControllerSettingsLedColorRainbow",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Rainbow",
"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": "ControllerSettingsSave", "ID": "ControllerSettingsSave",
"Translations": { "Translations": {
@@ -12847,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": {
+1 -1
View File
@@ -149,7 +149,7 @@ namespace Ryujinx.Headless
IgnoreMissingServices = configurationState.System.IgnoreMissingServices; IgnoreMissingServices = configurationState.System.IgnoreMissingServices;
if (NeedsOverride(nameof(IgnoreControllerApplet))) if (NeedsOverride(nameof(IgnoreControllerApplet)))
IgnoreControllerApplet = configurationState.IgnoreApplet; IgnoreControllerApplet = configurationState.System.IgnoreApplet;
return; return;
+6
View File
@@ -1,5 +1,6 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -143,6 +144,11 @@ namespace Ryujinx.Ava.Input
} }
} }
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
}
public void SetTriggerThreshold(float triggerThreshold) { } public void SetTriggerThreshold(float triggerThreshold) { }
public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { } public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { }
@@ -59,6 +59,8 @@ namespace Ryujinx.Ava.Input
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]); return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
} }
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
+6
View File
@@ -1,4 +1,5 @@
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Drawing; using System.Drawing;
@@ -74,6 +75,11 @@ namespace Ryujinx.Ava.Input
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SetLed(uint packedRgb)
{
Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaMouse");
}
public void SetTriggerThreshold(float triggerThreshold) public void SetTriggerThreshold(float triggerThreshold)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
+3
View File
@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using MouseButton = Ryujinx.Input.MouseButton; using MouseButton = Ryujinx.Input.MouseButton;
using Size = System.Drawing.Size; using Size = System.Drawing.Size;
@@ -134,6 +135,8 @@ namespace Ryujinx.Ava.Input
return new AvaloniaMouse(this); return new AvaloniaMouse(this);
} }
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
public void Dispose() public void Dispose()
{ {
if (_isDisposed) if (_isDisposed)
+2 -2
View File
@@ -47,9 +47,9 @@ namespace Ryujinx.Ava
{ {
Version = ReleaseInformation.Version; Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041))
{ {
_ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); _ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 20H1 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
} }
PreviewerDetached = true; PreviewerDetached = true;
+1 -2
View File
@@ -6,7 +6,6 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common; using Ryujinx.Common;
@@ -42,7 +41,7 @@ namespace Ryujinx.Ava.UI.Applet
bool okPressed = false; bool okPressed = false;
if (ConfigurationState.Instance.IgnoreApplet) if (ConfigurationState.Instance.System.IgnoreApplet)
return false; return false;
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
@@ -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}"
@@ -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 })
@@ -388,30 +388,6 @@ namespace Ryujinx.Ava.UI.Models.Input
} }
} }
private bool _enableLedChanging;
public bool EnableLedChanging
{
get => _enableLedChanging;
set
{
_enableLedChanging = value;
OnPropertyChanged();
}
}
private Color _ledColor;
public Color LedColor
{
get => _ledColor;
set
{
_ledColor = value;
OnPropertyChanged();
}
}
private bool _enableMotion; private bool _enableMotion;
public bool EnableMotion public bool EnableMotion
{ {
@@ -433,6 +409,58 @@ namespace Ryujinx.Ava.UI.Models.Input
OnPropertyChanged(); OnPropertyChanged();
} }
} }
private bool _enableLedChanging;
public bool EnableLedChanging
{
get => _enableLedChanging;
set
{
_enableLedChanging = value;
OnPropertyChanged();
}
}
public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed;
private bool _turnOffLed;
public bool TurnOffLed
{
get => _turnOffLed;
set
{
_turnOffLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
private bool _useRainbowLed;
public bool UseRainbowLed
{
get => _useRainbowLed;
set
{
_useRainbowLed = value;
OnPropertyChanged();
OnPropertyChanged(nameof(ShowLedColorPicker));
}
}
private Color _ledColor;
public Color LedColor
{
get => _ledColor;
set
{
_ledColor = value;
OnPropertyChanged();
}
}
public GamepadInputConfig(InputConfig config) public GamepadInputConfig(InputConfig config)
{ {
@@ -512,6 +540,8 @@ namespace Ryujinx.Ava.UI.Models.Input
if (controllerInput.Led != null) if (controllerInput.Led != null)
{ {
EnableLedChanging = controllerInput.Led.EnableLed; EnableLedChanging = controllerInput.Led.EnableLed;
TurnOffLed = controllerInput.Led.TurnOffLed;
UseRainbowLed = controllerInput.Led.UseRainbow;
uint rawColor = controllerInput.Led.LedColor; uint rawColor = controllerInput.Led.LedColor;
byte alpha = (byte)(rawColor >> 24); byte alpha = (byte)(rawColor >> 24);
byte red = (byte)(rawColor >> 16); byte red = (byte)(rawColor >> 16);
@@ -579,6 +609,8 @@ namespace Ryujinx.Ava.UI.Models.Input
Led = new LedConfigController Led = new LedConfigController
{ {
EnableLed = EnableLedChanging, EnableLed = EnableLedChanging,
TurnOffLed = this.TurnOffLed,
UseRainbow = UseRainbowLed,
LedColor = LedColor.ToUInt32() LedColor = LedColor.ToUInt32()
}, },
Version = InputConfig.CurrentVersion, Version = InputConfig.CurrentVersion,
@@ -432,7 +432,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
try try
{ {
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json")); HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json"));
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@@ -451,7 +451,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
try try
{ {
HttpResponseMessage response = await _httpClient.GetAsync($"https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json"); HttpResponseMessage response = await _httpClient.GetAsync($"https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/assets/amiibo/Amiibo.json");
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
@@ -1,5 +1,8 @@
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Views.Input;
@@ -57,6 +60,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
await RumbleInputView.Show(this); await RumbleInputView.Show(this);
} }
public RelayCommand LedDisabledChanged => Commands.Create(() =>
{
if (!Config.EnableLedChanging) return;
if (Config.TurnOffLed)
ParentModel.SelectedGamepad.ClearLed();
else
ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32());
});
public void OnParentModelChanged() public void OnParentModelChanged()
{ {
IsLeft = ParentModel.IsLeft; IsLeft = ParentModel.IsLeft;
@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using Avalonia.Threading; using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Input; using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
@@ -54,7 +55,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public IGamepadDriver AvaloniaKeyboardDriver { get; } public IGamepadDriver AvaloniaKeyboardDriver { get; }
public IGamepad SelectedGamepad { get; private set; }
private IGamepad _selectedGamepad;
public IGamepad SelectedGamepad
{
get => _selectedGamepad;
private set
{
_selectedGamepad = value;
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
}
}
public ObservableCollection<PlayerModel> PlayerIndexes { get; set; } public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
@@ -68,8 +80,9 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public bool IsKeyboard => !IsController; public bool IsKeyboard => !IsController;
public bool IsRight { get; set; } public bool IsRight { get; set; }
public bool IsLeft { get; set; } public bool IsLeft { get; set; }
public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led); public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
public bool IsModified { get; set; } public bool IsModified { get; set; }
public event Action NotifyChangesEvent; public event Action NotifyChangesEvent;
@@ -488,7 +488,6 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableDiscordIntegration = config.EnableDiscordIntegration; EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart; CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit; ShowConfirmExit = config.ShowConfirmExit;
IgnoreApplet = config.IgnoreApplet;
RememberWindowState = config.RememberWindowState; RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar; ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value; HideCursor = (int)config.HideCursor.Value;
@@ -532,6 +531,7 @@ namespace Ryujinx.Ava.UI.ViewModels
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks; EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
DramSize = config.System.DramSize; DramSize = config.System.DramSize;
IgnoreMissingServices = config.System.IgnoreMissingServices; IgnoreMissingServices = config.System.IgnoreMissingServices;
IgnoreApplet = config.System.IgnoreApplet;
// CPU // CPU
EnablePptc = config.System.EnablePtc; EnablePptc = config.System.EnablePtc;
@@ -591,7 +591,6 @@ namespace Ryujinx.Ava.UI.ViewModels
config.EnableDiscordIntegration.Value = EnableDiscordIntegration; config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart; config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit; config.ShowConfirmExit.Value = ShowConfirmExit;
config.IgnoreApplet.Value = IgnoreApplet;
config.RememberWindowState.Value = RememberWindowState; config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar; config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor; config.HideCursor.Value = (HideCursorMode)HideCursor;
@@ -632,12 +631,10 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds()); config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
config.Graphics.VSyncMode.Value = VSyncMode;
config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval;
config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval;
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks; config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
config.System.DramSize.Value = DramSize; config.System.DramSize.Value = DramSize;
config.System.IgnoreMissingServices.Value = IgnoreMissingServices; config.System.IgnoreMissingServices.Value = IgnoreMissingServices;
config.System.IgnoreApplet.Value = IgnoreApplet;
// CPU // CPU
config.System.EnablePtc.Value = EnablePptc; config.System.EnablePtc.Value = EnablePptc;
@@ -646,6 +643,9 @@ namespace Ryujinx.Ava.UI.ViewModels
config.System.UseHypervisor.Value = UseHypervisor; config.System.UseHypervisor.Value = UseHypervisor;
// Graphics // Graphics
config.Graphics.VSyncMode.Value = VSyncMode;
config.Graphics.EnableCustomVSyncInterval.Value = EnableCustomVSyncInterval;
config.Graphics.CustomVSyncInterval.Value = CustomVSyncInterval;
config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex; config.Graphics.GraphicsBackend.Value = (GraphicsBackend)GraphicsBackendIndex;
config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex); config.Graphics.PreferredGpu.Value = _gpuIds.ElementAtOrDefault(PreferredGpuIndex);
config.Graphics.EnableShaderCache.Value = EnableShaderCache; config.Graphics.EnableShaderCache.Value = EnableShaderCache;
@@ -429,7 +429,7 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- Motion + Rumble --> <!-- Motion, Rumble, LED -->
<StackPanel <StackPanel
Margin="0,10,0,0" Margin="0,10,0,0"
Spacing="5" Spacing="5"
@@ -495,25 +495,47 @@
Margin="0,-1,0,0"> Margin="0,-1,0,0">
<Grid IsVisible="{Binding ParentModel.HasLed}"> <Grid IsVisible="{Binding ParentModel.HasLed}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<CheckBox <CheckBox
Margin="10" Margin="10, 10, 5, 10"
MinWidth="0" MinWidth="0"
Grid.Column="0" Grid.Column="0"
IsChecked="{Binding Config.EnableLedChanging, Mode=TwoWay}"> IsChecked="{Binding Config.EnableLedChanging, Mode=TwoWay}">
<TextBlock Text="Custom LED color" /> <TextBlock Text="{ext:Locale ControllerSettingsLedColor}" />
</CheckBox>
<CheckBox
Margin="5, 10, 5, 10"
MinWidth="0"
Grid.Column="1"
IsVisible="{Binding ParentModel.CanClearLed}"
IsChecked="{Binding Config.TurnOffLed, Mode=TwoWay}"
Command="{Binding LedDisabledChanged}">
<TextBlock Text="{ext:Locale ControllerSettingsLedColorDisable}" />
</CheckBox>
<CheckBox
Margin="5, 10 5,10"
MinWidth="0"
Grid.Column="2"
IsEnabled="{Binding !Config.TurnOffLed}"
IsChecked="{Binding Config.UseRainbowLed, Mode=TwoWay}">
<TextBlock Text="{ext:Locale ControllerSettingsLedColorRainbow}" />
</CheckBox> </CheckBox>
<ui:ColorPickerButton <ui:ColorPickerButton
Grid.Column="1" Grid.Column="3"
Margin="10" IsEnabled="{Binding Config.ShowLedColorPicker}"
Margin="5, 10, 10, 10"
IsMoreButtonVisible="False" IsMoreButtonVisible="False"
UseColorPalette="False" UseColorPalette="False"
UseColorTriangle="False" UseColorTriangle="False"
UseColorWheel="False" UseColorWheel="False"
ShowAcceptDismissButtons="False" ShowAcceptDismissButtons="False"
IsAlphaEnabled="False" IsAlphaEnabled="False"
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
ColorChanged="ColorPickerButton_OnColorChanged"
Color="{Binding Config.LedColor, Mode=TwoWay}"> Color="{Binding Config.LedColor, Mode=TwoWay}">
</ui:ColorPickerButton> </ui:ColorPickerButton>
</Grid> </Grid>
@@ -4,11 +4,14 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels.Input; using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System.Linq;
using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
namespace Ryujinx.Ava.UI.Views.Input namespace Ryujinx.Ava.UI.Views.Input
@@ -82,7 +85,7 @@ namespace Ryujinx.Ava.UI.Views.Input
private void Button_IsCheckedChanged(object sender, RoutedEventArgs e) private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
{ {
if (sender is ToggleButton button) if (sender is ToggleButton button)
{ {
if (button.IsChecked is true) if (button.IsChecked is true)
{ {
@@ -103,7 +106,9 @@ namespace Ryujinx.Ava.UI.Views.Input
var viewModel = (DataContext as ControllerInputViewModel); var viewModel = (DataContext as ControllerInputViewModel);
IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations. IKeyboard keyboard =
(IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver
.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
IButtonAssigner assigner = CreateButtonAssigner(isStick); IButtonAssigner assigner = CreateButtonAssigner(isStick);
_currentAssigner.ButtonAssigned += (sender, e) => _currentAssigner.ButtonAssigned += (sender, e) =>
@@ -231,8 +236,31 @@ namespace Ryujinx.Ava.UI.Views.Input
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
{
gamepad?.ClearLed();
}
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
} }
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
{
if (!args.NewColor.HasValue) return;
if (DataContext is not ControllerInputViewModel cVm) return;
if (!cVm.Config.EnableLedChanging) return;
cVm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
}
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (DataContext is not ControllerInputViewModel cVm) return;
if (!cVm.Config.EnableLedChanging) return;
cVm.ParentModel.SelectedGamepad.SetLed(cVm.Config.LedColor.ToUInt32());
}
} }
} }
@@ -264,19 +264,19 @@
Name="FaqMenuItem" Name="FaqMenuItem"
Header="{ext:Locale MenuBarHelpFaq}" Header="{ext:Locale MenuBarHelpFaq}"
Icon="{ext:Icon fa-github}" Icon="{ext:Icon fa-github}"
CommandParameter="https://github.com/GreemDev/Ryujinx/wiki/FAQ-and-Troubleshooting" CommandParameter="https://github.com/Ryubing/Ryujinx/wiki/FAQ-and-Troubleshooting"
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" /> ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
<MenuItem <MenuItem
Name="SetupGuideMenuItem" Name="SetupGuideMenuItem"
Header="{ext:Locale MenuBarHelpSetup}" Header="{ext:Locale MenuBarHelpSetup}"
Icon="{ext:Icon fa-github}" Icon="{ext:Icon fa-github}"
CommandParameter="https://github.com/GreemDev/Ryujinx/wiki/Ryujinx-Setup-&amp;-Configuration-Guide" CommandParameter="https://github.com/Ryubing/Ryujinx/wiki/Ryujinx-Setup-&amp;-Configuration-Guide"
ToolTip.Tip="{ext:Locale MenuBarHelpSetupTooltip}" /> ToolTip.Tip="{ext:Locale MenuBarHelpSetupTooltip}" />
<MenuItem <MenuItem
Name="LdnGuideMenuItem" Name="LdnGuideMenuItem"
Header="{ext:Locale MenuBarHelpMultiplayer}" Header="{ext:Locale MenuBarHelpMultiplayer}"
Icon="{ext:Icon fa-github}" Icon="{ext:Icon fa-github}"
CommandParameter="https://github.com/GreemDev/Ryujinx/wiki/Multiplayer%E2%80%90(LDN%E2%80%90Local%E2%80%90Wireless)%E2%80%90Guide" CommandParameter="https://github.com/Ryubing/Ryujinx/wiki/Multiplayer%E2%80%90(LDN%E2%80%90Local%E2%80%90Wireless)%E2%80%90Guide"
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" /> ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
+1 -1
View File
@@ -182,7 +182,7 @@
HorizontalAlignment="Left" HorizontalAlignment="Left"
Background="Transparent" Background="Transparent"
Click="Button_OnClick" Click="Button_OnClick"
Tag="https://github.com/GreemDev/Ryujinx/graphs/contributors?type=a"> Tag="https://github.com/Ryubing/Ryujinx/graphs/contributors?type=a">
<TextBlock <TextBlock
FontSize="10" FontSize="10"
Text="{ext:Locale AboutRyujinxContributorsButtonHeader}" Text="{ext:Locale AboutRyujinxContributorsButtonHeader}"
+1 -3
View File
@@ -736,9 +736,7 @@ namespace Ryujinx.Ava.UI.Windows
}); });
} }
private static bool _intelMacWarningShown = !(OperatingSystem.IsMacOS() && private static bool _intelMacWarningShown = !RunningPlatform.IsIntelMac;
(RuntimeInformation.OSArchitecture == Architecture.X64 ||
RuntimeInformation.OSArchitecture == Architecture.X86));
public static async Task ShowIntelMacWarningAsync() public static async Task ShowIntelMacWarningAsync()
{ {
@@ -4,9 +4,14 @@ using Avalonia.Input;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Input;
using System; using System;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows namespace Ryujinx.Ava.UI.Windows
{ {
@@ -106,6 +111,12 @@ namespace Ryujinx.Ava.UI.Windows
protected override void OnClosing(WindowClosingEventArgs e) protected override void OnClosing(WindowClosingEventArgs e)
{ {
HotkeysPage.Dispose(); HotkeysPage.Dispose();
foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
{
gamepad?.ClearLed();
}
InputPage.Dispose(); InputPage.Dispose();
base.OnClosing(e); base.OnClosing(e);
} }
+4
View File
@@ -118,6 +118,8 @@ namespace Ryujinx.Ava
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion)); OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion));
} }
} }
Logger.Info?.Print(LogClass.Application, "Up to date.");
_running = false; _running = false;
@@ -188,6 +190,8 @@ namespace Ryujinx.Ava
OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion)); OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion));
} }
} }
Logger.Info?.Print(LogClass.Application, "Up to date.");
_running = false; _running = false;
@@ -1,3 +1,4 @@
using Avalonia.Media;
using Gommon; using Gommon;
using Ryujinx.Ava.Utilities.Configuration.System; using Ryujinx.Ava.Utilities.Configuration.System;
using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Ava.Utilities.Configuration.UI;
@@ -45,7 +46,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration; EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart; CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
ShowConfirmExit.Value = cff.ShowConfirmExit; ShowConfirmExit.Value = cff.ShowConfirmExit;
IgnoreApplet.Value = cff.IgnoreApplet;
RememberWindowState.Value = cff.RememberWindowState; RememberWindowState.Value = cff.RememberWindowState;
ShowTitleBar.Value = cff.ShowTitleBar; ShowTitleBar.Value = cff.ShowTitleBar;
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration; EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
@@ -97,6 +97,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.MemoryManagerMode.Value = cff.MemoryManagerMode; System.MemoryManagerMode.Value = cff.MemoryManagerMode;
System.DramSize.Value = cff.DramSize; System.DramSize.Value = cff.DramSize;
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices; System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
System.IgnoreApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor; System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn; UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
@@ -421,7 +422,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
config.Led = new LedConfigController config.Led = new LedConfigController
{ {
EnableLed = false, EnableLed = false,
LedColor = 328189 TurnOffLed = false,
UseRainbow = false,
LedColor = new Color(255, 5, 1, 253).ToUInt32()
}; };
} }
}) })
@@ -366,6 +366,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enable or disable ignoring missing services /// Enable or disable ignoring missing services
/// </summary> /// </summary>
public ReactiveObject<bool> IgnoreMissingServices { get; private set; } public ReactiveObject<bool> IgnoreMissingServices { get; private set; }
/// <summary>
/// Ignore Controller Applet
/// </summary>
public ReactiveObject<bool> IgnoreApplet { get; private set; }
/// <summary> /// <summary>
/// Uses Hypervisor over JIT if available /// Uses Hypervisor over JIT if available
@@ -404,6 +409,8 @@ namespace Ryujinx.Ava.Utilities.Configuration
DramSize.LogChangesToValue(nameof(DramSize)); DramSize.LogChangesToValue(nameof(DramSize));
IgnoreMissingServices = new ReactiveObject<bool>(); IgnoreMissingServices = new ReactiveObject<bool>();
IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices)); IgnoreMissingServices.LogChangesToValue(nameof(IgnoreMissingServices));
IgnoreApplet = new ReactiveObject<bool>();
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet));
AudioVolume = new ReactiveObject<float>(); AudioVolume = new ReactiveObject<float>();
AudioVolume.LogChangesToValue(nameof(AudioVolume)); AudioVolume.LogChangesToValue(nameof(AudioVolume));
UseHypervisor = new ReactiveObject<bool>(); UseHypervisor = new ReactiveObject<bool>();
@@ -745,11 +752,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// </summary> /// </summary>
public ReactiveObject<bool> ShowConfirmExit { get; private set; } public ReactiveObject<bool> ShowConfirmExit { get; private set; }
/// <summary>
/// Ignore Applet
/// </summary>
public ReactiveObject<bool> IgnoreApplet { get; private set; }
/// <summary> /// <summary>
/// Enables or disables save window size, position and state on close. /// Enables or disables save window size, position and state on close.
/// </summary> /// </summary>
@@ -782,8 +784,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration = new ReactiveObject<bool>(); EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>(); CheckUpdatesOnStart = new ReactiveObject<bool>();
ShowConfirmExit = new ReactiveObject<bool>(); ShowConfirmExit = new ReactiveObject<bool>();
IgnoreApplet = new ReactiveObject<bool>();
IgnoreApplet.LogChangesToValue(nameof(IgnoreApplet));
RememberWindowState = new ReactiveObject<bool>(); RememberWindowState = new ReactiveObject<bool>();
ShowTitleBar = new ReactiveObject<bool>(); ShowTitleBar = new ReactiveObject<bool>();
EnableHardwareAcceleration = new ReactiveObject<bool>(); EnableHardwareAcceleration = new ReactiveObject<bool>();
@@ -56,7 +56,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration = EnableDiscordIntegration, EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart, CheckUpdatesOnStart = CheckUpdatesOnStart,
ShowConfirmExit = ShowConfirmExit, ShowConfirmExit = ShowConfirmExit,
IgnoreApplet = IgnoreApplet,
RememberWindowState = RememberWindowState, RememberWindowState = RememberWindowState,
ShowTitleBar = ShowTitleBar, ShowTitleBar = ShowTitleBar,
EnableHardwareAcceleration = EnableHardwareAcceleration, EnableHardwareAcceleration = EnableHardwareAcceleration,
@@ -78,6 +77,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
MemoryManagerMode = System.MemoryManagerMode, MemoryManagerMode = System.MemoryManagerMode,
DramSize = System.DramSize, DramSize = System.DramSize,
IgnoreMissingServices = System.IgnoreMissingServices, IgnoreMissingServices = System.IgnoreMissingServices,
IgnoreApplet = System.IgnoreApplet,
UseHypervisor = System.UseHypervisor, UseHypervisor = System.UseHypervisor,
GuiColumns = new GuiColumns GuiColumns = new GuiColumns
{ {
@@ -176,7 +176,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration.Value = true; EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true; CheckUpdatesOnStart.Value = true;
ShowConfirmExit.Value = true; ShowConfirmExit.Value = true;
IgnoreApplet.Value = false;
RememberWindowState.Value = true; RememberWindowState.Value = true;
ShowTitleBar.Value = !OperatingSystem.IsWindows(); ShowTitleBar.Value = !OperatingSystem.IsWindows();
EnableHardwareAcceleration.Value = true; EnableHardwareAcceleration.Value = true;
@@ -200,6 +199,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe; System.MemoryManagerMode.Value = MemoryManagerMode.HostMappedUnsafe;
System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB; System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB;
System.IgnoreMissingServices.Value = false; System.IgnoreMissingServices.Value = false;
System.IgnoreApplet.Value = false;
System.UseHypervisor.Value = true; System.UseHypervisor.Value = true;
Multiplayer.LanInterfaceId.Value = "0"; Multiplayer.LanInterfaceId.Value = "0";
Multiplayer.Mode.Value = MultiplayerMode.Disabled; Multiplayer.Mode.Value = MultiplayerMode.Disabled;