Compare commits

...

17 Commits

Author SHA1 Message Date
Coxxs
aef45fe9ff Merge branch 'exception-minidump' into 'master'
Create minidump at ProcessUnhandledException

See merge request [ryubing/ryujinx!217](https://git.ryujinx.app/ryubing/ryujinx/-/merge_requests/217)
2026-04-01 11:42:07 -05:00
LotP
3ad4d4a692 Accurate Service Names (ryubing/ryujinx!296)
See merge request ryubing/ryujinx!296
2026-04-01 11:08:10 -05:00
Ryujinx Administrator
6fe7fb8dcb [ci skip] Lock GLI to v2.0.30 in Stable workflow 2026-03-28 22:44:55 -05:00
Ryujinx Administrator
fc357d3ba4 [ci skip] Lock GLI to v2.0.30 in Canary workflow 2026-03-28 22:43:58 -05:00
yeager
32ee806070 Updated Swedish translation (ryubing/ryujinx!289)
See merge request ryubing/ryujinx!289
2026-03-18 00:13:47 -05:00
sh0inx
4e81a4c2f4 [HLE] Added "null" check for isAtRest (ryubing/ryujinx!287)
See merge request ryubing/ryujinx!287
2026-03-15 09:46:36 -05:00
Coxxs
9cae62096a HLE: Implement CreateContextForSystem (ryubing/ryujinx!285)
See merge request ryubing/ryujinx!285
2026-03-14 13:57:49 -05:00
BowedCascade
648b609ebb Add restart emulation command (ryubing/ryujinx!276)
See merge request ryubing/ryujinx!276
2026-03-14 13:56:20 -05:00
BowedCascade
5ae86fc493 Fix keys file overwrite on installation and method name typo (ryubing/ryujinx!268)
See merge request ryubing/ryujinx!268
2026-03-14 13:52:58 -05:00
KeatonTheBot
6f90e47a73 UI: Restore FluentAvaloniaUI package, disable animations on app initialization (ryubing/ryujinx!256)
See merge request ryubing/ryujinx!256
2026-03-14 13:48:59 -05:00
sh0inx
ac5f9857e2 HLE: Implement IHidServer IsSixAxisSensorAtRest (ryubing/ryujinx!228)
See merge request ryubing/ryujinx!228
2026-03-14 13:25:55 -05:00
KeatonTheBot
4b42087bd4 Linux: Fix file picker not launching from disabling core dumps (ryubing/ryujinx!249)
See merge request ryubing/ryujinx!249
2026-03-06 19:04:42 -06:00
EscoDev
80cbf5d1fc Fix incorrect save button locale in user editor (ryubing/ryujinx!280)
See merge request ryubing/ryujinx!280
2026-03-01 15:48:29 -06:00
LotP
cc6d2dc162 fix nacp language buffer (ryubing/ryujinx!281)
See merge request ryubing/ryujinx!281
2026-02-25 13:58:31 -06:00
Coxxs
55c2ae2b3d Check if Switch is running before creating minidump 2025-12-22 12:28:21 +08:00
Coxxs
b51999a1ba Print a message first in case it crashes again during minidump creation 2025-12-22 12:28:21 +08:00
Coxxs
bfc0d62732 Create minidump at ProcessUnhandledException 2025-12-22 12:28:21 +08:00
32 changed files with 349 additions and 100 deletions

View File

@@ -50,7 +50,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -162,7 +162,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -215,7 +215,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH

View File

@@ -44,7 +44,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -161,7 +161,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -217,7 +217,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH

View File

@@ -3,13 +3,13 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.6" /> <PackageVersion Include="Avalonia" Version="11.3.12" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" /> <PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" /> <PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" /> <PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" /> <PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
@@ -22,7 +22,7 @@
<PackageVersion Include="Concentus" Version="2.2.2" /> <PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" /> <PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.1" /> <PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" /> <PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" />
<PackageVersion Include="Humanizer" Version="2.14.1" /> <PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
@@ -41,7 +41,7 @@
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" /> <PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
<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.LibHac" Version="0.21.0-alpha.126" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" /> <PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" /> <PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
<PackageVersion Include="Gommon" Version="2.8.0.1" /> <PackageVersion Include="Gommon" Version="2.8.0.1" />

View File

@@ -575,6 +575,31 @@
"zh_TW": "停止模擬" "zh_TW": "停止模擬"
} }
}, },
{
"ID": "MenuBarOptionsRestartEmulation",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Restart Emulation",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "Starta om emulering",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "MenuBarOptionsSettings", "ID": "MenuBarOptionsSettings",
"Translations": { "Translations": {
@@ -11300,6 +11325,31 @@
"zh_TW": "刪除" "zh_TW": "刪除"
} }
}, },
{
"ID": "UserProfilesSave",
"Translations": {
"ar_SA": "",
"de_DE": "Speichern",
"el_GR": "",
"en_US": "Save",
"es_ES": "",
"fr_FR": "",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "",
"sv_SE": "Spara",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "",
"zh_TW": ""
}
},
{ {
"ID": "UserProfilesClose", "ID": "UserProfilesClose",
"Translations": { "Translations": {
@@ -24851,4 +24901,4 @@
} }
} }
] ]
} }

View File

@@ -22,10 +22,11 @@ namespace Ryujinx.Common.Utilities
} }
// "dumpable" attribute of the calling process // "dumpable" attribute of the calling process
private const int PR_GET_DUMPABLE = 3;
private const int PR_SET_DUMPABLE = 4; private const int PR_SET_DUMPABLE = 4;
[DllImport("libc", SetLastError = true)] [LibraryImport("libc", SetLastError = true)]
private static extern int prctl(int option, int arg2); private static partial int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable) public static void SetCoreDumpable(bool dumpable)
{ {
@@ -36,5 +37,13 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(result == 0); Debug.Assert(result == 0);
} }
} }
// Use the below line to display dumpable status in the console:
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
public static bool IsCoreDumpable()
{
int result = prctl(PR_GET_DUMPABLE, 0);
return result == 1;
}
} }
} }

View File

@@ -488,6 +488,8 @@ namespace Ryujinx.HLE.FileSystem
if (keyPaths.Length is 0) if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files."); throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
List<string> failedFiles = new();
foreach (string filePath in keyPaths) foreach (string filePath in keyPaths)
{ {
try try
@@ -497,17 +499,20 @@ namespace Ryujinx.HLE.FileSystem
catch (Exception e) catch (Exception e)
{ {
Logger.Error?.Print(LogClass.Application, e.Message); Logger.Error?.Print(LogClass.Application, e.Message);
failedFiles.Add(Path.GetFileName(filePath));
continue; continue;
} }
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath)); string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
if (File.Exists(destPath))
File.Delete(destPath);
File.Copy(filePath, destPath, true); File.Copy(filePath, destPath, true);
} }
if (failedFiles.Count > 0)
{
throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
}
return; return;
} }
@@ -518,8 +523,6 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(keysSource); FileInfo info = new(keysSource);
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is not ".keys") if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys"); throw new InvalidFirmwarePackageException("Input file extension is not .keys");
@@ -534,10 +537,6 @@ namespace Ryujinx.HLE.FileSystem
string dest = Path.Combine(installDirectory, info.Name); string dest = Path.Combine(installDirectory, info.Name);
if (File.Exists(dest))
File.Delete(dest);
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
File.Copy(keysSource, dest, true); File.Copy(keysSource, dest, true);
} }
@@ -1059,7 +1058,7 @@ namespace Ryujinx.HLE.FileSystem
} }
} }
public static bool AreKeysAlredyPresent(string pathToCheck) public static bool AreKeysAlreadyPresent(string pathToCheck)
{ {
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"]; string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
foreach (string file in fileNames) foreach (string file in fileNames)

View File

@@ -246,21 +246,21 @@ namespace Ryujinx.HLE.HOS
public void InitializeServices() public void InitializeServices()
{ {
SmRegistry = new SmRegistry(); SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry)); SmServer = new ServerBase(KernelContext, "Sm", () => new IUserInterface(KernelContext, SmRegistry));
// Wait until SM server thread is done with initialization, // Wait until SM server thread is done with initialization,
// only then doing connections to SM is safe. // only then doing connections to SM is safe.
SmServer.InitDone.WaitOne(); SmServer.InitDone.WaitOne();
BsdServer = new ServerBase(KernelContext, "BsdServer"); BsdServer = new ServerBase(KernelContext, "Bsd");
FsServer = new ServerBase(KernelContext, "FsServer"); FsServer = new ServerBase(KernelContext, "Fs");
HidServer = new ServerBase(KernelContext, "HidServer"); HidServer = new ServerBase(KernelContext, "Hid");
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer"); NvDrvServer = new ServerBase(KernelContext, "Nv");
TimeServer = new ServerBase(KernelContext, "TimeServer"); TimeServer = new ServerBase(KernelContext, "Time");
ViServer = new ServerBase(KernelContext, "ViServerU"); ViServer = new ServerBase(KernelContext, "Vi:u");
ViServerM = new ServerBase(KernelContext, "ViServerM"); ViServerM = new ServerBase(KernelContext, "Vi:m");
ViServerS = new ServerBase(KernelContext, "ViServerS"); ViServerS = new ServerBase(KernelContext, "Vi:s");
LdnServer = new ServerBase(KernelContext, "LdnServer"); LdnServer = new ServerBase(KernelContext, "Ldn");
StartNewServices(); StartNewServices();
} }
@@ -286,7 +286,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationFlags.Is64Bit | ProcessCreationFlags.Is64Bit |
ProcessCreationFlags.PoolPartitionSystem; ProcessCreationFlags.PoolPartitionSystem;
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); ProcessCreationInfo creationInfo = new(service.Name, 1, 0, 0x8000000, 1, Flags, 0, 0);
uint[] defaultCapabilities = uint[] defaultCapabilities =
[ [
@@ -570,6 +570,11 @@ namespace Ryujinx.HLE.HOS
} }
} }
public string DebugGetApplicationProcessMinidump()
{
return DebugGetApplicationProcess()?.Debugger?.GetMinidump();
}
internal KProcess DebugGetApplicationProcess() internal KProcess DebugGetApplicationProcess()
{ {
lock (KernelContext.Processes) lock (KernelContext.Processes)

View File

@@ -56,6 +56,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_activeCount = 0; _activeCount = 0;
JoyHold = NpadJoyHoldType.Vertical; JoyHold = NpadJoyHoldType.Vertical;
SixAxisActive = false;
} }
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
@@ -580,6 +581,29 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return needUpdateRight; return needUpdateRight;
} }
public bool isAtRest(int playerNumber)
{
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
if (currentNpad.StyleSet == NpadStyleTag.None)
{
return true; // it will always be at rest because it cannot move.
}
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
float acceleration = Math.Abs(storage.Acceleration.X)
+ Math.Abs(storage.Acceleration.Y)
+ Math.Abs(storage.Acceleration.Z);
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
+ Math.Abs(storage.AngularVelocity.Y)
+ Math.Abs(storage.AngularVelocity.Z);
// TODO: check against config deadzone and add sensitivity setting
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
}
private void UpdateDisconnectedInputSixAxis(PlayerIndex index) private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{ {

View File

@@ -602,19 +602,33 @@ namespace Ryujinx.HLE.HOS.Services.Hid
} }
[CommandCmif(82)] [CommandCmif(82)]
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context) public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
{ {
int sixAxisSensorHandle = context.RequestData.ReadInt32(); int sixAxisSensorHandle = context.RequestData.ReadInt32();
// 4 byte struct w/ 4-byte alignment
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
// 32bit sign extension padding -> if = 0, + offset, else - offset
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
context.RequestData.BaseStream.Position += 4; // Padding context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
bool isAtRest = true; // TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented
// We currently do not support stopping or starting SixAxisTracking.
context.ResponseData.Write(isAtRest);
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
return ResultCode.Success; return ResultCode.Success;
} }
@@ -629,7 +643,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor); context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor }); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -79,9 +79,15 @@ namespace Ryujinx.HLE.HOS.Services
ProcessCreationFlags.Is64Bit | ProcessCreationFlags.Is64Bit |
ProcessCreationFlags.PoolPartitionSystem; ProcessCreationFlags.PoolPartitionSystem;
ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0); ProcessCreationInfo creationInfo = new(Name, 1, 0, 0x8000000, 1, Flags, 0, 0);
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main); KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, () =>
{
var currentThread = KernelStatic.GetCurrentThread();
currentThread.HostThread.Name = $"{{{Name}}}";
Main();
});
} }
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)

View File

@@ -17,13 +17,12 @@ namespace Ryujinx.HLE.HOS.Services.Sm
private static readonly Dictionary<string, Type> _services; private static readonly Dictionary<string, Type> _services;
private readonly SmRegistry _registry; private readonly SmRegistry _registry;
private readonly ServerBase _commonServer; private ServerBase _commonServer;
private bool _isInitialized; private bool _isInitialized;
public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true) public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true)
{ {
_commonServer = new ServerBase(context, "CommonServer");
_registry = registry; _registry = registry;
} }
@@ -97,6 +96,11 @@ namespace Ryujinx.HLE.HOS.Services.Sm
IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter); IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter);
if (_commonServer is null)
{
_commonServer = new ServerBase(context.Device.System.KernelContext, "Common");
}
service.TrySetServer(_commonServer); service.TrySetServer(_commonServer);
service.Server.AddSessionObj(session.ServerSession, service); service.Server.AddSessionObj(session.ServerSession, service);
} }
@@ -253,7 +257,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
public override void DestroyAtExit() public override void DestroyAtExit()
{ {
_commonServer.Dispose(); _commonServer?.Dispose();
base.DestroyAtExit(); base.DestroyAtExit();
} }

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
public ISslService(ServiceCtx context) { } public ISslService(ServiceCtx context) { }
[CommandCmif(0)] [CommandCmif(0)]
// CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext> // CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext>
public ResultCode CreateContext(ServiceCtx context) public ResultCode CreateContext(ServiceCtx context)
{ {
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32(); SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
@@ -126,14 +126,18 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
} }
[CommandCmif(100)] [CommandCmif(100)]
// CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64) // CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContextForSystem>
public ResultCode CreateContextForSystem(ServiceCtx context) public ResultCode CreateContextForSystem(ServiceCtx context)
{ {
ulong pid = context.RequestData.ReadUInt64();
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32(); SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong pidPlaceholder = context.RequestData.ReadUInt64(); ulong pidPlaceholder = context.RequestData.ReadUInt64();
#pragma warning restore IDE0059
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder }); // Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class.
MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -20,5 +20,7 @@ namespace Ryujinx.HLE.HOS.SystemState
SimplifiedChinese, SimplifiedChinese,
TraditionalChinese, TraditionalChinese,
BrazilianPortuguese, BrazilianPortuguese,
Polish,
Thai,
} }
} }

View File

@@ -23,7 +23,9 @@ namespace Ryujinx.HLE.HOS.SystemState
"es-419", "es-419",
"zh-Hans", "zh-Hans",
"zh-Hant", "zh-Hant",
"pt-BR" "pt-BR",
"pl",
"th"
]; ];
internal long DesiredKeyboardLayout { get; private set; } internal long DesiredKeyboardLayout { get; private set; }

View File

@@ -18,5 +18,7 @@ namespace Ryujinx.HLE.HOS.SystemState
TraditionalChinese, TraditionalChinese,
SimplifiedChinese, SimplifiedChinese,
BrazilianPortuguese, BrazilianPortuguese,
Polish,
Thai,
} }
} }

View File

@@ -1,12 +1,19 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers.Binary;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Ryujinx.Horizon.Sdk.Ns namespace Ryujinx.Horizon.Sdk.Ns
{ {
public struct ApplicationControlProperty public struct ApplicationControlProperty
{ {
public Array16<ApplicationTitle> Title; /// <summary>
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
/// </summary>
public Array16<ApplicationTitle> TitleBlock;
public Array37<byte> Isbn; public Array37<byte> Isbn;
public StartupUserAccountValue StartupUserAccount; public StartupUserAccountValue StartupUserAccount;
public UserAccountSwitchLockValue UserAccountSwitchLock; public UserAccountSwitchLockValue UserAccountSwitchLock;
@@ -58,7 +65,10 @@ namespace Ryujinx.Horizon.Sdk.Ns
public RepairFlagValue RepairFlag; public RepairFlagValue RepairFlag;
public byte ProgramIndex; public byte ProgramIndex;
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag; public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
public Array4<byte> Reserved3214; public byte ApplicationErrorCodePrefix;
public TitleCompressionValue TitleCompression;
public byte AcdIndex;
public byte ApparentPlatform;
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration; public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
public ApplicationJitConfiguration JitConfiguration; public ApplicationJitConfiguration JitConfiguration;
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors; public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
@@ -74,6 +84,47 @@ namespace Ryujinx.Horizon.Sdk.Ns
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0'); public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0'); public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0'); public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
private const int TitleCount = 32;
private const int TitleEntrySize = 0x300;
/// <summary>
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
/// </summary>
public readonly ApplicationTitle[] Title
{
get
{
var titles = new ApplicationTitle[TitleCount];
if (TitleCompression != TitleCompressionValue.Enable)
{
TitleBlock.AsSpan().CopyTo(titles);
return titles;
}
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
}
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
return titles;
}
}
public struct ApplicationTitle public struct ApplicationTitle
{ {
@@ -130,6 +181,8 @@ namespace Ryujinx.Horizon.Sdk.Ns
TraditionalChinese = 13, TraditionalChinese = 13,
SimplifiedChinese = 14, SimplifiedChinese = 14,
BrazilianPortuguese = 15, BrazilianPortuguese = 15,
Polish = 16,
Thai = 17,
} }
public enum Organization public enum Organization
@@ -302,5 +355,11 @@ namespace Ryujinx.Horizon.Sdk.Ns
Deny = 0, Deny = 0,
Allow = 1, Allow = 1,
} }
public enum TitleCompressionValue : byte
{
Disable = 0,
Enable = 1,
}
} }
} }

View File

@@ -9,12 +9,14 @@ namespace Ryujinx.Horizon
private readonly Action<ServiceTable> _entrypoint; private readonly Action<ServiceTable> _entrypoint;
private readonly ServiceTable _serviceTable; private readonly ServiceTable _serviceTable;
private readonly HorizonOptions _options; private readonly HorizonOptions _options;
public readonly string Name;
internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options) internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options, string name)
{ {
_entrypoint = entrypoint; _entrypoint = entrypoint;
_serviceTable = serviceTable; _serviceTable = serviceTable;
_options = options; _options = options;
Name = name;
} }
public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext) public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext)

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.Horizon
void RegisterService<T>() where T : IService void RegisterService<T>() where T : IService
{ {
entries.Add(new ServiceEntry(T.Main, this, options)); entries.Add(new ServiceEntry(T.Main, this, options, typeof(T).Name));
} }
RegisterService<ArpMain>(); RegisterService<ArpMain>();

View File

@@ -42,6 +42,7 @@ namespace Ryujinx.Ava
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; } public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; } public static string BackendThreadingArg { get; private set; }
public static bool CoreDumpArg { get; private set; }
private const uint MbIconwarning = 0x30; private const uint MbIconwarning = 0x30;
@@ -81,6 +82,8 @@ namespace Ryujinx.Ava
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui"); bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui");
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps"); bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
CoreDumpArg = coreDumpArg;
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception. // TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
// This is undesirable and causes very odd behavior during development (the process stops responding, // This is undesirable and causes very odd behavior during development (the process stops responding,
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user. // the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
@@ -383,6 +386,30 @@ namespace Ryujinx.Ava
exceptions.Add(initialException); exceptions.Add(initialException);
} }
if (isTerminating && HLE.Switch.Shared is { } device)
{
try
{
// Print a short message first just in case it crashes again during minidump creation (should not happen)
Logger.Error?.Print(LogClass.Application, $"Unhandled exception caught: {initialException.GetType().Name}. Creating guest program minidump...");
var minidump = device.System?.DebugGetApplicationProcessMinidump();
if (minidump == null)
{
Logger.Warning?.Print(LogClass.Application, "Failed to create minidump");
}
else
{
Logger.Info?.Print(LogClass.Application, minidump);
}
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, $"Failed to create minidump: {e.Message}");
}
}
foreach (Exception e in exceptions) foreach (Exception e in exceptions)
{ {
string message = $"Unhandled exception caught: {e}"; string message = $"Unhandled exception caught: {e}";

View File

@@ -49,7 +49,7 @@
<PackageReference Include="Svg.Controls.Avalonia" /> <PackageReference Include="Svg.Controls.Avalonia" />
<PackageReference Include="Svg.Controls.Skia.Avalonia" /> <PackageReference Include="Svg.Controls.Skia.Avalonia" />
<PackageReference Include="DynamicData" /> <PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI.NoAnim" /> <PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="CommandLineParser" /> <PackageReference Include="CommandLineParser" />
<PackageReference Include="CommunityToolkit.Mvvm" /> <PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="DiscordRichPresence" /> <PackageReference Include="DiscordRichPresence" />

View File

@@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Name)) if (string.IsNullOrWhiteSpace(data.Name))
{ {
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title) foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{ {
if (!controlTitle.NameString.IsEmpty()) if (!controlTitle.NameString.IsEmpty())
{ {
@@ -1417,7 +1417,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Developer)) if (string.IsNullOrWhiteSpace(data.Developer))
{ {
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title) foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{ {
if (!controlTitle.PublisherString.IsEmpty()) if (!controlTitle.PublisherString.IsEmpty())
{ {

View File

@@ -24,6 +24,8 @@ namespace Ryujinx.Ava.Systems.Configuration.System
SimplifiedChinese, SimplifiedChinese,
TraditionalChinese, TraditionalChinese,
BrazilianPortuguese, BrazilianPortuguese,
Polish,
Thai,
} }
public static class LanguageEnumHelper public static class LanguageEnumHelper

View File

@@ -5,6 +5,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Windowing; using FluentAvalonia.UI.Windowing;
using Gommon; using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
@@ -53,6 +54,9 @@ namespace Ryujinx.Ava
{ {
Name = FormatTitle(); Name = FormatTitle();
// Disable menu animations
FAUISettings.SetAnimationsEnabledAtAppLevel(false);
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())

View File

@@ -174,6 +174,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _screenshotKey = "F8"; private string _screenshotKey = "F8";
private float _volume; private float _volume;
private ApplicationData _currentApplicationData; private ApplicationData _currentApplicationData;
private bool _pendingRestart;
private readonly AutoResetEvent _rendererWaitEvent; private readonly AutoResetEvent _rendererWaitEvent;
private int _customVSyncInterval; private int _customVSyncInterval;
private int _customVSyncIntervalPercentageProxy; private int _customVSyncIntervalPercentageProxy;
@@ -1062,7 +1063,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string dialogMessage = string dialogMessage =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
if (ContentManager.AreKeysAlredyPresent(systemDirectory)) if (ContentManager.AreKeysAlreadyPresent(systemDirectory))
{ {
dialogMessage += dialogMessage +=
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys
@@ -1250,6 +1251,14 @@ namespace Ryujinx.Ava.UI.ViewModels
await LoadApplication(_currentApplicationData); await LoadApplication(_currentApplicationData);
} }
else if (_pendingRestart)
{
_pendingRestart = false;
Logger.Info?.Print(LogClass.Application, $"Restarting emulation for '{_currentApplicationData.Name}'");
await LoadApplication(_currentApplicationData);
}
else else
{ {
// Otherwise, clear state. // Otherwise, clear state.
@@ -1258,6 +1267,21 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public void RestartEmulation()
{
if (AppHost is null || _currentApplicationData is null)
{
Logger.Warning?.Print(LogClass.Application, "RestartEmulation called but no application is running.");
return;
}
Logger.Info?.Print(LogClass.Application, $"Restart requested for '{_currentApplicationData.Name}'");
_pendingRestart = true;
AppHost.Stop();
}
private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) private void Update_StatusBar(object sender, StatusUpdatedEventArgs args)
{ {
if (ShowMenuAndStatusBar && !ShowLoadProgress) if (ShowMenuAndStatusBar && !ShowLoadProgress)

View File

@@ -47,18 +47,13 @@
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}"> <StackPanel Orientation="Horizontal" IsEnabled="{Binding ShowLedColorPicker}">
<TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" /> <TextBlock MinWidth="75" MaxWidth="200" Text="{ext:Locale ControllerSettingsLedColor}" />
<ui:ColorPickerButton <ColorPicker
Margin="5" Margin="5"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False" IsAlphaEnabled="False"
AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree" AttachedToVisualTree="ColorPicker_OnAttachedToVisualTree"
ColorChanged="ColorPickerButton_OnColorChanged" ColorChanged="ColorPicker_OnColorChanged"
Color="{Binding LedColor, Mode=TwoWay}"> Color="{Binding LedColor, Mode=TwoWay}">
</ui:ColorPickerButton> </ColorPicker>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -1,4 +1,5 @@
using Avalonia; using Avalonia;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Controls;
@@ -30,19 +31,17 @@ namespace Ryujinx.UI.Views.Input
InitializeComponent(); InitializeComponent();
} }
private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args) private void ColorPicker_OnColorChanged(object sender, ColorChangedEventArgs args)
{ {
if (!args.NewColor.HasValue)
return;
if (!ViewModel.EnableLedChanging) if (!ViewModel.EnableLedChanging)
return; return;
if (ViewModel.TurnOffLed) if (ViewModel.TurnOffLed)
return; return;
ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32()); ViewModel.ParentModel.SelectedGamepad.SetLed(args.NewColor.ToUInt32());
} }
private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e) private void ColorPicker_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{ {
if (!ViewModel.EnableLedChanging) if (!ViewModel.EnableLedChanging)
return; return;

View File

@@ -167,6 +167,12 @@
Icon="{ext:Icon fa-solid fa-stop}" Icon="{ext:Icon fa-solid fa-stop}"
InputGesture="Escape" InputGesture="Escape"
IsEnabled="{Binding IsGameRunning}" /> IsEnabled="{Binding IsGameRunning}" />
<MenuItem
Name="RestartEmulationMenuItem"
Header="{ext:Locale MenuBarOptionsRestartEmulation}"
Icon="{ext:Icon fa-solid fa-rotate-right}"
InputGesture="Ctrl + R"
IsEnabled="{Binding IsGameRunning}" />
<MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" /> <MenuItem Command="{Binding SimulateWakeUpMessage}" Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Icon="{ext:Icon fa-solid fa-sun}" />
<Separator /> <Separator />
<MenuItem <MenuItem

View File

@@ -43,6 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Main
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause()); PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume()); ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted()); StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation());
CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp); CheatManagerMenuItem.Command = Commands.CreateSilentFail(OpenCheatManagerForCurrentApp);
InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes); InstallFileTypesMenuItem.Command = Commands.Create(InstallFileTypes);
UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes); UninstallFileTypesMenuItem.Command = Commands.Create(UninstallFileTypes);

View File

@@ -108,7 +108,7 @@
<Button <Button
Name="SaveButton" Name="SaveButton"
Click="SaveButton_Click"> Click="SaveButton_Click">
<TextBlock Text="{ext:Locale UserProfilesSetProfileImage}" /> <TextBlock Text="{ext:Locale UserProfilesSave}" />
</Button> </Button>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@@ -78,22 +78,16 @@
Spacing="10" Spacing="10"
Margin="0 24 0 0" Margin="0 24 0 0"
HorizontalAlignment="Right"> HorizontalAlignment="Right">
<ui:ColorPickerButton <ColorPicker
FlyoutPlacement="Top"
IsMoreButtonVisible="False"
UseColorPalette="False"
UseColorTriangle="False"
UseColorWheel="False"
ShowAcceptDismissButtons="False"
IsAlphaEnabled="False" IsAlphaEnabled="False"
Color="{Binding BackgroundColor, Mode=TwoWay}" Color="{Binding BackgroundColor, Mode=TwoWay}"
Name="ColorButton"> Name="ColorButton">
<ui:ColorPickerButton.Styles> <ColorPicker.Styles>
<Style Selector="Grid#Root > DockPanel > Grid"> <Style Selector="Grid#Root > DockPanel > Grid">
<Setter Property="IsVisible" Value="False" /> <Setter Property="IsVisible" Value="False" />
</Style> </Style>
</ui:ColorPickerButton.Styles> </ColorPicker.Styles>
</ui:ColorPickerButton> </ColorPicker>
<Button <Button
Content="{ext:Locale AvatarChoose}" Content="{ext:Locale AvatarChoose}"
Height="35" Height="35"

View File

@@ -41,6 +41,7 @@
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" /> <KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" /> <KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" /> <KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" /> <KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" /> <KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
</Window.KeyBindings> </Window.KeyBindings>

View File

@@ -1,5 +1,7 @@
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Gommon; using Gommon;
using Ryujinx.Common.Utilities;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -11,29 +13,42 @@ namespace Ryujinx.Ava.Utilities
extension(IStorageProvider storageProvider) extension(IStorageProvider storageProvider)
{ {
public Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(FolderPickerOpenOptions openOptions = null) => public Task<Optional<IStorageFolder>> OpenSingleFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false)) CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, false)))
.Then(folders => folders.FindFirst()); .Then(folders => folders.FindFirst());
public Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(FilePickerOpenOptions openOptions = null) => public Task<Optional<IStorageFile>> OpenSingleFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false)) CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, false)))
.Then(files => files.FindFirst()); .Then(files => files.FindFirst());
public Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(FolderPickerOpenOptions openOptions = null) => public Task<Optional<IReadOnlyList<IStorageFolder>>> OpenMultiFolderPickerAsync(FolderPickerOpenOptions openOptions = null) =>
storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true)) CoreDumpable(() => storageProvider.OpenFolderPickerAsync(FixOpenOptions(openOptions, true)))
.Then(folders => folders.Count > 0 ? Optional.Of(folders) : default); .Then(folders => folders.Count > 0 ? Optional.Of(folders) : default);
public Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(FilePickerOpenOptions openOptions = null) => public Task<Optional<IReadOnlyList<IStorageFile>>> OpenMultiFilePickerAsync(FilePickerOpenOptions openOptions = null) =>
storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true)) CoreDumpable(() => storageProvider.OpenFilePickerAsync(FixOpenOptions(openOptions, true)))
.Then(files => files.Count > 0 ? Optional.Of(files) : default); .Then(files => files.Count > 0 ? Optional.Of(files) : default);
} }
private static async Task<T> CoreDumpable<T>(Func<Task<T>> picker)
{
OsUtils.SetCoreDumpable(true);
try
{
return await picker();
}
finally
{
if (!Program.CoreDumpArg)
OsUtils.SetCoreDumpable(false);
}
}
private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple) private static FilePickerOpenOptions FixOpenOptions(this FilePickerOpenOptions openOptions, bool allowMultiple)
{ {
if (openOptions is null) if (openOptions is null)
return new FilePickerOpenOptions { AllowMultiple = allowMultiple }; return new FilePickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple; openOptions.AllowMultiple = allowMultiple;
return openOptions; return openOptions;
} }
@@ -43,7 +58,6 @@ namespace Ryujinx.Ava.Utilities
return new FolderPickerOpenOptions { AllowMultiple = allowMultiple }; return new FolderPickerOpenOptions { AllowMultiple = allowMultiple };
openOptions.AllowMultiple = allowMultiple; openOptions.AllowMultiple = allowMultiple;
return openOptions; return openOptions;
} }
} }